@stencil/angular-output-target 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -1
- package/angular-component-lib/utils.ts +9 -15
- package/dist/generate-angular-component.d.ts +22 -2
- package/dist/generate-angular-component.js +127 -94
- package/dist/generate-value-accessors.js +2 -5
- package/dist/index.cjs.js +233 -117
- package/dist/index.js +233 -117
- package/dist/output-angular.js +61 -17
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +2 -5
- package/dist/types.d.ts +9 -2
- package/dist/utils.d.ts +27 -1
- package/dist/utils.js +45 -0
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -73,110 +73,188 @@ async function readPackageJson(config, rootDir) {
|
|
|
73
73
|
}
|
|
74
74
|
return pkgData;
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
`selector: \'${cmpMeta.tagName}\'`,
|
|
93
|
-
`changeDetection: ChangeDetectionStrategy.OnPush`,
|
|
94
|
-
`template: '<ng-content></ng-content>'`,
|
|
95
|
-
];
|
|
96
|
-
if (inputs.length > 0) {
|
|
97
|
-
directiveOpts.push(`inputs: ['${inputs.join(`', '`)}']`);
|
|
76
|
+
/**
|
|
77
|
+
* Formats an array of strings to a string of quoted, comma separated values.
|
|
78
|
+
* @param list The list of unformatted strings to format
|
|
79
|
+
* @returns The formatted array of strings. (e.g. ['foo', 'bar']) => `'foo', 'bar'`
|
|
80
|
+
*/
|
|
81
|
+
const formatToQuotedList = (list) => list.map((item) => `'${item}'`).join(', ');
|
|
82
|
+
/**
|
|
83
|
+
* Creates an import statement for a list of named imports from a module.
|
|
84
|
+
* @param imports The list of named imports.
|
|
85
|
+
* @param module The module to import from.
|
|
86
|
+
*
|
|
87
|
+
* @returns The import statement as a string.
|
|
88
|
+
*/
|
|
89
|
+
const createImportStatement = (imports, module) => {
|
|
90
|
+
if (imports.length === 0) {
|
|
91
|
+
return '';
|
|
98
92
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
93
|
+
return `import { ${imports.join(', ')} } from '${module}';`;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Creates the collection of import statements for a component based on the component's events type dependencies.
|
|
97
|
+
* @param componentTagName The tag name of the component (pascal case).
|
|
98
|
+
* @param events The events compiler metadata.
|
|
99
|
+
* @param options The options for generating the import statements (e.g. whether to import from the custom elements directory).
|
|
100
|
+
* @returns The import statements as an array of strings.
|
|
101
|
+
*/
|
|
102
|
+
const createComponentEventTypeImports = (componentTagName, events, options) => {
|
|
103
|
+
const { componentCorePackage, includeImportCustomElements, customElementsDir } = options;
|
|
104
|
+
const imports = [];
|
|
105
|
+
const namedImports = new Set();
|
|
106
|
+
const importPathName = normalizePath(componentCorePackage) + (includeImportCustomElements ? `/${customElementsDir || 'components'}` : '');
|
|
107
|
+
events.forEach((event) => {
|
|
108
|
+
Object.entries(event.complexType.references).forEach(([typeName, refObject]) => {
|
|
108
109
|
if (refObject.location === 'local' || refObject.location === 'import') {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
const newTypeName = `I${componentTagName}${typeName}`;
|
|
111
|
+
// Prevents duplicate imports for the same type.
|
|
112
|
+
if (!namedImports.has(newTypeName)) {
|
|
113
|
+
imports.push(`import type { ${typeName} as ${newTypeName} } from '${importPathName}';`);
|
|
114
|
+
namedImports.add(newTypeName);
|
|
114
115
|
}
|
|
115
|
-
outputsInterface.add(`import type { ${reference} as ${remappedReference} } from '${importLocation}';`);
|
|
116
116
|
}
|
|
117
117
|
});
|
|
118
118
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
componentEvents.push(` /**
|
|
125
|
-
* ${output.docs.text} ${output.docs.tags.map((tag) => `@${tag.name} ${tag.text}`)}
|
|
126
|
-
*/`);
|
|
127
|
-
/**
|
|
128
|
-
* The original attribute contains the original type defined by the devs.
|
|
129
|
-
* This regexp normalizes the reference, by removing linebreaks,
|
|
130
|
-
* replacing consecutive spaces with a single space, and adding a single space after commas.
|
|
131
|
-
**/
|
|
132
|
-
const outputTypeRemapped = Object.entries(outputReferenceRemap).reduce((type, [src, dst]) => {
|
|
133
|
-
return type
|
|
134
|
-
.replace(new RegExp(`^${src}$`, 'g'), `${dst}`)
|
|
135
|
-
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (v, p1, p2) => [p1, dst, p2].join(''));
|
|
136
|
-
}, output.complexType.original
|
|
137
|
-
.replace(/\n/g, ' ')
|
|
138
|
-
.replace(/\s{2,}/g, ' ')
|
|
139
|
-
.replace(/,\s*/g, ', '));
|
|
140
|
-
componentEvents.push(` ${output.name}: EventEmitter<CustomEvent<${outputTypeRemapped.trim()}>>;`);
|
|
141
|
-
if (index === outputs.length - 1) {
|
|
142
|
-
// Empty line to push end `}` to new line
|
|
143
|
-
componentEvents.push('\n');
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
const lines = [
|
|
147
|
-
'',
|
|
148
|
-
`${[...outputsInterface].join('\n')}
|
|
149
|
-
export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {${componentEvents.length > 1 ? componentEvents.join('\n') : ''}}
|
|
119
|
+
return imports.join('\n');
|
|
120
|
+
};
|
|
121
|
+
const EXTENDED_PATH_REGEX = /^\\\\\?\\/;
|
|
122
|
+
const NON_ASCII_REGEX = /[^\x00-\x80]+/;
|
|
123
|
+
const SLASH_REGEX = /\\/g;
|
|
150
124
|
|
|
151
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Creates an Angular component declaration from formatted Stencil compiler metadata.
|
|
127
|
+
*
|
|
128
|
+
* @param tagName The tag name of the component.
|
|
129
|
+
* @param inputs The inputs of the Stencil component (e.g. ['myInput']).
|
|
130
|
+
* @param outputs The outputs/events of the Stencil component. (e.g. ['myOutput']).
|
|
131
|
+
* @param methods The methods of the Stencil component. (e.g. ['myMethod']).
|
|
132
|
+
* @param includeImportCustomElements Whether to define the component as a custom element.
|
|
133
|
+
* @returns The component declaration as a string.
|
|
134
|
+
*/
|
|
135
|
+
const createAngularComponentDefinition = (tagName, inputs, outputs, methods, includeImportCustomElements = false) => {
|
|
136
|
+
const tagNameAsPascal = dashToPascalCase(tagName);
|
|
137
|
+
const hasInputs = inputs.length > 0;
|
|
138
|
+
const hasOutputs = outputs.length > 0;
|
|
139
|
+
const hasMethods = methods.length > 0;
|
|
140
|
+
// Formats the input strings into comma separated, single quoted values.
|
|
141
|
+
const formattedInputs = formatToQuotedList(inputs);
|
|
142
|
+
// Formats the output strings into comma separated, single quoted values.
|
|
143
|
+
const formattedOutputs = formatToQuotedList(outputs);
|
|
144
|
+
// Formats the method strings into comma separated, single quoted values.
|
|
145
|
+
const formattedMethods = formatToQuotedList(methods);
|
|
146
|
+
const proxyCmpOptions = [];
|
|
147
|
+
if (includeImportCustomElements) {
|
|
148
|
+
const defineCustomElementFn = `define${tagNameAsPascal}`;
|
|
149
|
+
proxyCmpOptions.push(`\n defineCustomElementFn: ${defineCustomElementFn}`);
|
|
150
|
+
}
|
|
151
|
+
if (hasInputs) {
|
|
152
|
+
proxyCmpOptions.push(`\n inputs: [${formattedInputs}]`);
|
|
153
|
+
}
|
|
154
|
+
if (hasMethods) {
|
|
155
|
+
proxyCmpOptions.push(`\n methods: [${formattedMethods}]`);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Notes on the generated output:
|
|
159
|
+
* - We disable @angular-eslint/no-inputs-metadata-property, so that
|
|
160
|
+
* Angular does not complain about the inputs property. The output target
|
|
161
|
+
* uses the inputs property to define the inputs of the component instead of
|
|
162
|
+
* having to use the @Input decorator (and manually define the type and default value).
|
|
163
|
+
*/
|
|
164
|
+
const output = `@ProxyCmp({${proxyCmpOptions.join(',')}\n})
|
|
152
165
|
@Component({
|
|
153
|
-
${
|
|
166
|
+
selector: '${tagName}',
|
|
167
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
168
|
+
template: '<ng-content></ng-content>',
|
|
169
|
+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
|
170
|
+
inputs: [${formattedInputs}],
|
|
154
171
|
})
|
|
155
|
-
export class ${tagNameAsPascal} {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
lines.push(` constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
|
172
|
+
export class ${tagNameAsPascal} {
|
|
173
|
+
protected el: HTMLElement;
|
|
174
|
+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
|
159
175
|
c.detach();
|
|
160
|
-
this.el = r.nativeElement
|
|
161
|
-
|
|
162
|
-
|
|
176
|
+
this.el = r.nativeElement;${hasOutputs
|
|
177
|
+
? `
|
|
178
|
+
proxyOutputs(this, this.el, [${formattedOutputs}]);`
|
|
179
|
+
: ''}
|
|
180
|
+
}
|
|
181
|
+
}`;
|
|
182
|
+
return output;
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Sanitizes and formats the component event type.
|
|
186
|
+
* @param componentClassName The class name of the component (e.g. 'MyComponent')
|
|
187
|
+
* @param event The Stencil component event.
|
|
188
|
+
* @returns The sanitized event type as a string.
|
|
189
|
+
*/
|
|
190
|
+
const formatOutputType = (componentClassName, event) => {
|
|
191
|
+
/**
|
|
192
|
+
* The original attribute contains the original type defined by the devs.
|
|
193
|
+
* This regexp normalizes the reference, by removing linebreaks,
|
|
194
|
+
* replacing consecutive spaces with a single space, and adding a single space after commas.
|
|
195
|
+
*/
|
|
196
|
+
return Object.entries(event.complexType.references)
|
|
197
|
+
.filter(([_, refObject]) => refObject.location === 'local' || refObject.location === 'import')
|
|
198
|
+
.reduce((type, [src, dst]) => {
|
|
199
|
+
const renamedType = `I${componentClassName}${type}`;
|
|
200
|
+
return (renamedType
|
|
201
|
+
.replace(new RegExp(`^${src}$`, 'g'), `${dst}`)
|
|
202
|
+
// Capture all instances of the `src` field surrounded by non-word characters on each side and join them.
|
|
203
|
+
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (v, p1, p2) => [p1, dst, p2].join('')));
|
|
204
|
+
}, event.complexType.original
|
|
205
|
+
.replace(/\n/g, ' ')
|
|
206
|
+
.replace(/\s{2,}/g, ' ')
|
|
207
|
+
.replace(/,\s*/g, ', '));
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Creates a formatted comment block based on the JS doc comment.
|
|
211
|
+
* @param doc The compiler jsdoc.
|
|
212
|
+
* @returns The formatted comment block as a string.
|
|
213
|
+
*/
|
|
214
|
+
const createDocComment = (doc) => {
|
|
215
|
+
if (doc.text.trim().length === 0 && doc.tags.length === 0) {
|
|
216
|
+
return '';
|
|
163
217
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
218
|
+
return `/**
|
|
219
|
+
* ${doc.text}${doc.tags.length > 0 ? ' ' : ''}${doc.tags.map((tag) => `@${tag.name} ${tag.text}`)}
|
|
220
|
+
*/`;
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Creates the component interface type definition.
|
|
224
|
+
* @param tagNameAsPascal The tag name as PascalCase.
|
|
225
|
+
* @param events The events to generate the interface properties for.
|
|
226
|
+
* @param componentCorePackage The component core package.
|
|
227
|
+
* @param includeImportCustomElements Whether to include the import for the custom element definition.
|
|
228
|
+
* @param customElementsDir The custom elements directory.
|
|
229
|
+
* @returns The component interface type definition as a string.
|
|
230
|
+
*/
|
|
231
|
+
const createComponentTypeDefinition = (tagNameAsPascal, events, componentCorePackage, includeImportCustomElements = false, customElementsDir) => {
|
|
232
|
+
const publicEvents = events.filter((ev) => !ev.internal);
|
|
233
|
+
const eventTypeImports = createComponentEventTypeImports(tagNameAsPascal, publicEvents, {
|
|
234
|
+
componentCorePackage,
|
|
235
|
+
includeImportCustomElements,
|
|
236
|
+
customElementsDir,
|
|
237
|
+
});
|
|
238
|
+
const eventTypes = publicEvents.map((event) => {
|
|
239
|
+
const comment = createDocComment(event.docs);
|
|
240
|
+
let eventName = event.name;
|
|
241
|
+
if (event.name.includes('-')) {
|
|
242
|
+
// If an event name includes a dash, we need to wrap it in quotes.
|
|
243
|
+
// https://github.com/ionic-team/stencil-ds-output-targets/issues/212
|
|
244
|
+
eventName = `'${event.name}'`;
|
|
245
|
+
}
|
|
246
|
+
return `${comment.length > 0 ? ` ${comment}` : ''}
|
|
247
|
+
${eventName}: EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>;`;
|
|
248
|
+
});
|
|
249
|
+
const interfaceDeclaration = `export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {`;
|
|
250
|
+
const typeDefinition = (eventTypeImports.length > 0 ? `${eventTypeImports + '\n\n'}` : '') +
|
|
251
|
+
`${interfaceDeclaration}${eventTypes.length === 0
|
|
252
|
+
? '}'
|
|
253
|
+
: `
|
|
254
|
+
${eventTypes.join('\n')}
|
|
255
|
+
}`}`;
|
|
256
|
+
return typeDefinition;
|
|
167
257
|
};
|
|
168
|
-
function getProxyCmp(tagName, includeCustomElement, inputs, methods) {
|
|
169
|
-
const hasInputs = inputs.length > 0;
|
|
170
|
-
const hasMethods = methods.length > 0;
|
|
171
|
-
const proxMeta = [
|
|
172
|
-
`defineCustomElementFn: ${includeCustomElement ? 'define' + dashToPascalCase(tagName) : 'undefined'}`
|
|
173
|
-
];
|
|
174
|
-
if (hasInputs)
|
|
175
|
-
proxMeta.push(`inputs: ['${inputs.join(`', '`)}']`);
|
|
176
|
-
if (hasMethods)
|
|
177
|
-
proxMeta.push(`methods: ['${methods.join(`', '`)}']`);
|
|
178
|
-
return `@ProxyCmp({\n ${proxMeta.join(',\n ')}\n})`;
|
|
179
|
-
}
|
|
180
258
|
|
|
181
259
|
function generateAngularDirectivesFile(compilerCtx, components, outputTarget) {
|
|
182
260
|
// Only create the file if it is defined in the stencil configuration
|
|
@@ -199,15 +277,12 @@ export const DIRECTIVES = [
|
|
|
199
277
|
}
|
|
200
278
|
|
|
201
279
|
async function generateValueAccessors(compilerCtx, components, outputTarget, config) {
|
|
202
|
-
if (!Array.isArray(outputTarget.valueAccessorConfigs) ||
|
|
203
|
-
outputTarget.valueAccessorConfigs.length === 0) {
|
|
280
|
+
if (!Array.isArray(outputTarget.valueAccessorConfigs) || outputTarget.valueAccessorConfigs.length === 0) {
|
|
204
281
|
return;
|
|
205
282
|
}
|
|
206
283
|
const targetDir = path.dirname(outputTarget.directivesProxyFile);
|
|
207
284
|
const normalizedValueAccessors = outputTarget.valueAccessorConfigs.reduce((allAccessors, va) => {
|
|
208
|
-
const elementSelectors = Array.isArray(va.elementSelectors)
|
|
209
|
-
? va.elementSelectors
|
|
210
|
-
: [va.elementSelectors];
|
|
285
|
+
const elementSelectors = Array.isArray(va.elementSelectors) ? va.elementSelectors : [va.elementSelectors];
|
|
211
286
|
const type = va.type;
|
|
212
287
|
let allElementSelectors = [];
|
|
213
288
|
let allEventTargets = [];
|
|
@@ -290,10 +365,26 @@ function generateProxies(components, pkgData, outputTarget, rootDir) {
|
|
|
290
365
|
const distTypesDir = path.dirname(pkgData.types);
|
|
291
366
|
const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
|
|
292
367
|
const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
|
|
368
|
+
/**
|
|
369
|
+
* The collection of named imports from @angular/core.
|
|
370
|
+
*/
|
|
371
|
+
const angularCoreImports = [
|
|
372
|
+
'ChangeDetectionStrategy',
|
|
373
|
+
'ChangeDetectorRef',
|
|
374
|
+
'Component',
|
|
375
|
+
'ElementRef',
|
|
376
|
+
'EventEmitter',
|
|
377
|
+
'NgZone',
|
|
378
|
+
];
|
|
379
|
+
/**
|
|
380
|
+
* The collection of named imports from the angular-component-lib/utils.
|
|
381
|
+
*/
|
|
382
|
+
const componentLibImports = ['ProxyCmp', 'proxyOutputs'];
|
|
293
383
|
const imports = `/* tslint:disable */
|
|
294
384
|
/* auto-generated angular directive proxies */
|
|
295
|
-
|
|
296
|
-
|
|
385
|
+
${createImportStatement(angularCoreImports, '@angular/core')}
|
|
386
|
+
|
|
387
|
+
${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`;
|
|
297
388
|
/**
|
|
298
389
|
* Generate JSX import type from correct location.
|
|
299
390
|
* When using custom elements build, we need to import from
|
|
@@ -301,8 +392,12 @@ import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';\n`;
|
|
|
301
392
|
* otherwise we risk bundlers pulling in lazy loaded imports.
|
|
302
393
|
*/
|
|
303
394
|
const generateTypeImports = () => {
|
|
304
|
-
let importLocation = outputTarget.componentCorePackage
|
|
305
|
-
|
|
395
|
+
let importLocation = outputTarget.componentCorePackage
|
|
396
|
+
? normalizePath(outputTarget.componentCorePackage)
|
|
397
|
+
: normalizePath(componentsTypeFile);
|
|
398
|
+
importLocation += outputTarget.includeImportCustomElements
|
|
399
|
+
? `/${outputTarget.customElementsDir || 'components'}`
|
|
400
|
+
: '';
|
|
306
401
|
return `import ${outputTarget.includeImportCustomElements ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
|
|
307
402
|
};
|
|
308
403
|
const typeImports = generateTypeImports();
|
|
@@ -314,21 +409,45 @@ import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';\n`;
|
|
|
314
409
|
* IonButton React Component that takes in the Web Component as a parameter.
|
|
315
410
|
*/
|
|
316
411
|
if (outputTarget.includeImportCustomElements && outputTarget.componentCorePackage !== undefined) {
|
|
317
|
-
const cmpImports = components.map(component => {
|
|
412
|
+
const cmpImports = components.map((component) => {
|
|
318
413
|
const pascalImport = dashToPascalCase(component.tagName);
|
|
319
|
-
return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir ||
|
|
320
|
-
'components'}/${component.tagName}.js';`;
|
|
414
|
+
return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir || 'components'}/${component.tagName}.js';`;
|
|
321
415
|
});
|
|
322
416
|
sourceImports = cmpImports.join('\n');
|
|
323
417
|
}
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
418
|
+
const proxyFileOutput = [];
|
|
419
|
+
const filterInternalProps = (prop) => !prop.internal;
|
|
420
|
+
const mapPropName = (prop) => prop.name;
|
|
421
|
+
const { includeImportCustomElements, componentCorePackage, customElementsDir } = outputTarget;
|
|
422
|
+
for (let cmpMeta of components) {
|
|
423
|
+
const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
|
|
424
|
+
const inputs = [];
|
|
425
|
+
if (cmpMeta.properties) {
|
|
426
|
+
inputs.push(...cmpMeta.properties.filter(filterInternalProps).map(mapPropName));
|
|
427
|
+
}
|
|
428
|
+
if (cmpMeta.virtualProperties) {
|
|
429
|
+
inputs.push(...cmpMeta.virtualProperties.map(mapPropName));
|
|
430
|
+
}
|
|
431
|
+
inputs.sort();
|
|
432
|
+
const outputs = [];
|
|
433
|
+
if (cmpMeta.events) {
|
|
434
|
+
outputs.push(...cmpMeta.events.filter(filterInternalProps).map(mapPropName));
|
|
435
|
+
}
|
|
436
|
+
const methods = [];
|
|
437
|
+
if (cmpMeta.methods) {
|
|
438
|
+
methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* For each component, we need to generate:
|
|
442
|
+
* 1. The @Component decorated class
|
|
443
|
+
* 2. The component interface (using declaration merging for types).
|
|
444
|
+
*/
|
|
445
|
+
const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, includeImportCustomElements);
|
|
446
|
+
const componentTypeDefinition = createComponentTypeDefinition(tagNameAsPascal, cmpMeta.events, componentCorePackage, includeImportCustomElements, customElementsDir);
|
|
447
|
+
proxyFileOutput.push(componentDefinition, '\n');
|
|
448
|
+
proxyFileOutput.push(componentTypeDefinition, '\n');
|
|
449
|
+
}
|
|
450
|
+
const final = [imports, typeImports, sourceImports, ...proxyFileOutput];
|
|
332
451
|
return final.join('\n') + '\n';
|
|
333
452
|
}
|
|
334
453
|
const GENERATED_DTS = 'components.d.ts';
|
|
@@ -347,12 +466,12 @@ const angularOutputTarget = (outputTarget) => ({
|
|
|
347
466
|
},
|
|
348
467
|
});
|
|
349
468
|
function normalizeOutputTarget(config, outputTarget) {
|
|
350
|
-
const results = Object.assign(Object.assign({}, outputTarget), { excludeComponents: outputTarget.excludeComponents || [],
|
|
469
|
+
const results = Object.assign(Object.assign({}, outputTarget), { excludeComponents: outputTarget.excludeComponents || [], valueAccessorConfigs: outputTarget.valueAccessorConfigs || [] });
|
|
351
470
|
if (config.rootDir == null) {
|
|
352
471
|
throw new Error('rootDir is not set and it should be set by stencil itself');
|
|
353
472
|
}
|
|
354
473
|
if (outputTarget.directivesProxyFile == null) {
|
|
355
|
-
throw new Error('directivesProxyFile is required');
|
|
474
|
+
throw new Error('directivesProxyFile is required. Please set it in the Stencil config.');
|
|
356
475
|
}
|
|
357
476
|
if (outputTarget.directivesProxyFile && !path.isAbsolute(outputTarget.directivesProxyFile)) {
|
|
358
477
|
results.directivesProxyFile = normalizePath(path.join(config.rootDir, outputTarget.directivesProxyFile));
|
|
@@ -360,9 +479,6 @@ function normalizeOutputTarget(config, outputTarget) {
|
|
|
360
479
|
if (outputTarget.directivesArrayFile && !path.isAbsolute(outputTarget.directivesArrayFile)) {
|
|
361
480
|
results.directivesArrayFile = normalizePath(path.join(config.rootDir, outputTarget.directivesArrayFile));
|
|
362
481
|
}
|
|
363
|
-
if (outputTarget.directivesUtilsFile && !path.isAbsolute(outputTarget.directivesUtilsFile)) {
|
|
364
|
-
results.directivesUtilsFile = normalizePath(path.join(config.rootDir, outputTarget.directivesUtilsFile));
|
|
365
|
-
}
|
|
366
482
|
return results;
|
|
367
483
|
}
|
|
368
484
|
|
package/dist/output-angular.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import { relativeImport, normalizePath, sortBy, readPackageJson, dashToPascalCase } from './utils';
|
|
3
|
-
import {
|
|
2
|
+
import { relativeImport, normalizePath, sortBy, readPackageJson, dashToPascalCase, createImportStatement, } from './utils';
|
|
3
|
+
import { createAngularComponentDefinition, createComponentTypeDefinition } from './generate-angular-component';
|
|
4
4
|
import { generateAngularDirectivesFile } from './generate-angular-directives-file';
|
|
5
5
|
import generateValueAccessors from './generate-value-accessors';
|
|
6
6
|
export async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
|
|
@@ -37,10 +37,26 @@ export function generateProxies(components, pkgData, outputTarget, rootDir) {
|
|
|
37
37
|
const distTypesDir = path.dirname(pkgData.types);
|
|
38
38
|
const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
|
|
39
39
|
const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
|
|
40
|
+
/**
|
|
41
|
+
* The collection of named imports from @angular/core.
|
|
42
|
+
*/
|
|
43
|
+
const angularCoreImports = [
|
|
44
|
+
'ChangeDetectionStrategy',
|
|
45
|
+
'ChangeDetectorRef',
|
|
46
|
+
'Component',
|
|
47
|
+
'ElementRef',
|
|
48
|
+
'EventEmitter',
|
|
49
|
+
'NgZone',
|
|
50
|
+
];
|
|
51
|
+
/**
|
|
52
|
+
* The collection of named imports from the angular-component-lib/utils.
|
|
53
|
+
*/
|
|
54
|
+
const componentLibImports = ['ProxyCmp', 'proxyOutputs'];
|
|
40
55
|
const imports = `/* tslint:disable */
|
|
41
56
|
/* auto-generated angular directive proxies */
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
${createImportStatement(angularCoreImports, '@angular/core')}
|
|
58
|
+
|
|
59
|
+
${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`;
|
|
44
60
|
/**
|
|
45
61
|
* Generate JSX import type from correct location.
|
|
46
62
|
* When using custom elements build, we need to import from
|
|
@@ -48,8 +64,12 @@ import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';\n`;
|
|
|
48
64
|
* otherwise we risk bundlers pulling in lazy loaded imports.
|
|
49
65
|
*/
|
|
50
66
|
const generateTypeImports = () => {
|
|
51
|
-
let importLocation = outputTarget.componentCorePackage
|
|
52
|
-
|
|
67
|
+
let importLocation = outputTarget.componentCorePackage
|
|
68
|
+
? normalizePath(outputTarget.componentCorePackage)
|
|
69
|
+
: normalizePath(componentsTypeFile);
|
|
70
|
+
importLocation += outputTarget.includeImportCustomElements
|
|
71
|
+
? `/${outputTarget.customElementsDir || 'components'}`
|
|
72
|
+
: '';
|
|
53
73
|
return `import ${outputTarget.includeImportCustomElements ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
|
|
54
74
|
};
|
|
55
75
|
const typeImports = generateTypeImports();
|
|
@@ -61,21 +81,45 @@ import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';\n`;
|
|
|
61
81
|
* IonButton React Component that takes in the Web Component as a parameter.
|
|
62
82
|
*/
|
|
63
83
|
if (outputTarget.includeImportCustomElements && outputTarget.componentCorePackage !== undefined) {
|
|
64
|
-
const cmpImports = components.map(component => {
|
|
84
|
+
const cmpImports = components.map((component) => {
|
|
65
85
|
const pascalImport = dashToPascalCase(component.tagName);
|
|
66
|
-
return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir ||
|
|
67
|
-
'components'}/${component.tagName}.js';`;
|
|
86
|
+
return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir || 'components'}/${component.tagName}.js';`;
|
|
68
87
|
});
|
|
69
88
|
sourceImports = cmpImports.join('\n');
|
|
70
89
|
}
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
const proxyFileOutput = [];
|
|
91
|
+
const filterInternalProps = (prop) => !prop.internal;
|
|
92
|
+
const mapPropName = (prop) => prop.name;
|
|
93
|
+
const { includeImportCustomElements, componentCorePackage, customElementsDir } = outputTarget;
|
|
94
|
+
for (let cmpMeta of components) {
|
|
95
|
+
const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
|
|
96
|
+
const inputs = [];
|
|
97
|
+
if (cmpMeta.properties) {
|
|
98
|
+
inputs.push(...cmpMeta.properties.filter(filterInternalProps).map(mapPropName));
|
|
99
|
+
}
|
|
100
|
+
if (cmpMeta.virtualProperties) {
|
|
101
|
+
inputs.push(...cmpMeta.virtualProperties.map(mapPropName));
|
|
102
|
+
}
|
|
103
|
+
inputs.sort();
|
|
104
|
+
const outputs = [];
|
|
105
|
+
if (cmpMeta.events) {
|
|
106
|
+
outputs.push(...cmpMeta.events.filter(filterInternalProps).map(mapPropName));
|
|
107
|
+
}
|
|
108
|
+
const methods = [];
|
|
109
|
+
if (cmpMeta.methods) {
|
|
110
|
+
methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* For each component, we need to generate:
|
|
114
|
+
* 1. The @Component decorated class
|
|
115
|
+
* 2. The component interface (using declaration merging for types).
|
|
116
|
+
*/
|
|
117
|
+
const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, includeImportCustomElements);
|
|
118
|
+
const componentTypeDefinition = createComponentTypeDefinition(tagNameAsPascal, cmpMeta.events, componentCorePackage, includeImportCustomElements, customElementsDir);
|
|
119
|
+
proxyFileOutput.push(componentDefinition, '\n');
|
|
120
|
+
proxyFileOutput.push(componentTypeDefinition, '\n');
|
|
121
|
+
}
|
|
122
|
+
const final = [imports, typeImports, sourceImports, ...proxyFileOutput];
|
|
79
123
|
return final.join('\n') + '\n';
|
|
80
124
|
}
|
|
81
125
|
const GENERATED_DTS = 'components.d.ts';
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { Config, OutputTargetCustom } from '@stencil/core/internal';
|
|
2
2
|
import type { OutputTargetAngular } from './types';
|
|
3
3
|
export declare const angularOutputTarget: (outputTarget: OutputTargetAngular) => OutputTargetCustom;
|
|
4
|
-
export declare function normalizeOutputTarget(config: Config, outputTarget:
|
|
4
|
+
export declare function normalizeOutputTarget(config: Config, outputTarget: OutputTargetAngular): OutputTargetAngular;
|
package/dist/plugin.js
CHANGED
|
@@ -14,12 +14,12 @@ export const angularOutputTarget = (outputTarget) => ({
|
|
|
14
14
|
},
|
|
15
15
|
});
|
|
16
16
|
export function normalizeOutputTarget(config, outputTarget) {
|
|
17
|
-
const results = Object.assign(Object.assign({}, outputTarget), { excludeComponents: outputTarget.excludeComponents || [],
|
|
17
|
+
const results = Object.assign(Object.assign({}, outputTarget), { excludeComponents: outputTarget.excludeComponents || [], valueAccessorConfigs: outputTarget.valueAccessorConfigs || [] });
|
|
18
18
|
if (config.rootDir == null) {
|
|
19
19
|
throw new Error('rootDir is not set and it should be set by stencil itself');
|
|
20
20
|
}
|
|
21
21
|
if (outputTarget.directivesProxyFile == null) {
|
|
22
|
-
throw new Error('directivesProxyFile is required');
|
|
22
|
+
throw new Error('directivesProxyFile is required. Please set it in the Stencil config.');
|
|
23
23
|
}
|
|
24
24
|
if (outputTarget.directivesProxyFile && !path.isAbsolute(outputTarget.directivesProxyFile)) {
|
|
25
25
|
results.directivesProxyFile = normalizePath(path.join(config.rootDir, outputTarget.directivesProxyFile));
|
|
@@ -27,8 +27,5 @@ export function normalizeOutputTarget(config, outputTarget) {
|
|
|
27
27
|
if (outputTarget.directivesArrayFile && !path.isAbsolute(outputTarget.directivesArrayFile)) {
|
|
28
28
|
results.directivesArrayFile = normalizePath(path.join(config.rootDir, outputTarget.directivesArrayFile));
|
|
29
29
|
}
|
|
30
|
-
if (outputTarget.directivesUtilsFile && !path.isAbsolute(outputTarget.directivesUtilsFile)) {
|
|
31
|
-
results.directivesUtilsFile = normalizePath(path.join(config.rootDir, outputTarget.directivesUtilsFile));
|
|
32
|
-
}
|
|
33
30
|
return results;
|
|
34
31
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
export interface OutputTargetAngular {
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* The package name of the component library.
|
|
4
|
+
* This is used to generate the import statements.
|
|
5
|
+
*/
|
|
6
|
+
componentCorePackage: string;
|
|
7
|
+
/**
|
|
8
|
+
* The path to the proxy file that will be generated. This can be an absolute path
|
|
9
|
+
* or a relative path from the root directory of the Stencil library.
|
|
10
|
+
*/
|
|
3
11
|
directivesProxyFile: string;
|
|
4
12
|
directivesArrayFile?: string;
|
|
5
|
-
directivesUtilsFile?: string;
|
|
6
13
|
valueAccessorConfigs?: ValueAccessorConfig[];
|
|
7
14
|
excludeComponents?: string[];
|
|
8
15
|
includeImportCustomElements?: boolean;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Config } from '@stencil/core/internal';
|
|
1
|
+
import { ComponentCompilerEvent, Config } from '@stencil/core/internal';
|
|
2
2
|
import type { PackageJSON } from './types';
|
|
3
3
|
export declare const toLowerCase: (str: string) => string;
|
|
4
4
|
export declare const dashToPascalCase: (str: string) => string;
|
|
@@ -7,3 +7,29 @@ export declare function normalizePath(str: string): string;
|
|
|
7
7
|
export declare function relativeImport(pathFrom: string, pathTo: string, ext?: string): string;
|
|
8
8
|
export declare function isRelativePath(path: string): boolean | "";
|
|
9
9
|
export declare function readPackageJson(config: Config, rootDir: string): Promise<PackageJSON>;
|
|
10
|
+
/**
|
|
11
|
+
* Formats an array of strings to a string of quoted, comma separated values.
|
|
12
|
+
* @param list The list of unformatted strings to format
|
|
13
|
+
* @returns The formatted array of strings. (e.g. ['foo', 'bar']) => `'foo', 'bar'`
|
|
14
|
+
*/
|
|
15
|
+
export declare const formatToQuotedList: (list: readonly string[]) => string;
|
|
16
|
+
/**
|
|
17
|
+
* Creates an import statement for a list of named imports from a module.
|
|
18
|
+
* @param imports The list of named imports.
|
|
19
|
+
* @param module The module to import from.
|
|
20
|
+
*
|
|
21
|
+
* @returns The import statement as a string.
|
|
22
|
+
*/
|
|
23
|
+
export declare const createImportStatement: (imports: string[], module: string) => string;
|
|
24
|
+
/**
|
|
25
|
+
* Creates the collection of import statements for a component based on the component's events type dependencies.
|
|
26
|
+
* @param componentTagName The tag name of the component (pascal case).
|
|
27
|
+
* @param events The events compiler metadata.
|
|
28
|
+
* @param options The options for generating the import statements (e.g. whether to import from the custom elements directory).
|
|
29
|
+
* @returns The import statements as an array of strings.
|
|
30
|
+
*/
|
|
31
|
+
export declare const createComponentEventTypeImports: (componentTagName: string, events: readonly ComponentCompilerEvent[], options: {
|
|
32
|
+
componentCorePackage: string;
|
|
33
|
+
includeImportCustomElements?: boolean;
|
|
34
|
+
customElementsDir?: string;
|
|
35
|
+
}) => string;
|