@stencil/angular-output-target 0.0.0-dev.11698339436.1d39048b
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/LICENSE.md +9 -0
- package/README.md +51 -0
- package/angular-component-lib/utils.ts +57 -0
- package/dist/generate-angular-component.d.ts +24 -0
- package/dist/generate-angular-component.js +153 -0
- package/dist/generate-angular-directives-file.d.ts +3 -0
- package/dist/generate-angular-directives-file.js +20 -0
- package/dist/generate-angular-modules.d.ts +6 -0
- package/dist/generate-angular-modules.js +17 -0
- package/dist/generate-value-accessors.d.ts +8 -0
- package/dist/generate-value-accessors.js +56 -0
- package/dist/index.cjs.js +560 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +552 -0
- package/dist/output-angular.d.ts +4 -0
- package/dist/output-angular.js +138 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.js +37 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.js +0 -0
- package/dist/utils.d.ts +44 -0
- package/dist/utils.js +138 -0
- package/package.json +65 -0
- package/resources/control-value-accessors/boolean-value-accessor.ts +27 -0
- package/resources/control-value-accessors/number-value-accessor.ts +29 -0
- package/resources/control-value-accessors/radio-value-accessor.ts +24 -0
- package/resources/control-value-accessors/select-value-accessor.ts +24 -0
- package/resources/control-value-accessors/text-value-accessor.ts +24 -0
- package/resources/control-value-accessors/value-accessor.ts +39 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var os = require('os');
|
|
7
|
+
|
|
8
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
9
|
+
|
|
10
|
+
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
11
|
+
|
|
12
|
+
const OutputTypes = {
|
|
13
|
+
Component: 'component',
|
|
14
|
+
Scam: 'scam',
|
|
15
|
+
Standalone: 'standalone',
|
|
16
|
+
};
|
|
17
|
+
const toLowerCase = (str) => str.toLowerCase();
|
|
18
|
+
const dashToPascalCase = (str) => toLowerCase(str)
|
|
19
|
+
.split('-')
|
|
20
|
+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
21
|
+
.join('');
|
|
22
|
+
function sortBy(array, prop) {
|
|
23
|
+
return array.slice().sort((a, b) => {
|
|
24
|
+
const nameA = prop(a);
|
|
25
|
+
const nameB = prop(b);
|
|
26
|
+
if (nameA < nameB)
|
|
27
|
+
return -1;
|
|
28
|
+
if (nameA > nameB)
|
|
29
|
+
return 1;
|
|
30
|
+
return 0;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function normalizePath(str) {
|
|
34
|
+
// Convert Windows backslash paths to slash paths: foo\\bar ➔ foo/bar
|
|
35
|
+
// https://github.com/sindresorhus/slash MIT
|
|
36
|
+
// By Sindre Sorhus
|
|
37
|
+
if (typeof str !== 'string') {
|
|
38
|
+
throw new Error(`invalid path to normalize`);
|
|
39
|
+
}
|
|
40
|
+
str = str.trim();
|
|
41
|
+
if (EXTENDED_PATH_REGEX.test(str) || NON_ASCII_REGEX.test(str)) {
|
|
42
|
+
return str;
|
|
43
|
+
}
|
|
44
|
+
str = str.replace(SLASH_REGEX, '/');
|
|
45
|
+
// always remove the trailing /
|
|
46
|
+
// this makes our file cache look ups consistent
|
|
47
|
+
if (str.charAt(str.length - 1) === '/') {
|
|
48
|
+
const colonIndex = str.indexOf(':');
|
|
49
|
+
if (colonIndex > -1) {
|
|
50
|
+
if (colonIndex < str.length - 2) {
|
|
51
|
+
str = str.substring(0, str.length - 1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (str.length > 1) {
|
|
55
|
+
str = str.substring(0, str.length - 1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return str;
|
|
59
|
+
}
|
|
60
|
+
function relativeImport(pathFrom, pathTo, ext) {
|
|
61
|
+
let relativePath = path__default['default'].relative(path__default['default'].dirname(pathFrom), path__default['default'].dirname(pathTo));
|
|
62
|
+
if (relativePath === '') {
|
|
63
|
+
relativePath = '.';
|
|
64
|
+
}
|
|
65
|
+
else if (relativePath[0] !== '.') {
|
|
66
|
+
relativePath = './' + relativePath;
|
|
67
|
+
}
|
|
68
|
+
return normalizePath(`${relativePath}/${path__default['default'].basename(pathTo, ext)}`);
|
|
69
|
+
}
|
|
70
|
+
async function readPackageJson(config, rootDir) {
|
|
71
|
+
var _a;
|
|
72
|
+
const pkgJsonPath = path__default['default'].join(rootDir, 'package.json');
|
|
73
|
+
let pkgJson;
|
|
74
|
+
try {
|
|
75
|
+
pkgJson = (await ((_a = config.sys) === null || _a === void 0 ? void 0 : _a.readFile(pkgJsonPath, 'utf8')));
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
throw new Error(`Missing "package.json" file for distribution: ${pkgJsonPath}`);
|
|
79
|
+
}
|
|
80
|
+
let pkgData;
|
|
81
|
+
try {
|
|
82
|
+
pkgData = JSON.parse(pkgJson);
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
throw new Error(`Error parsing package.json: ${pkgJsonPath}, ${e}`);
|
|
86
|
+
}
|
|
87
|
+
return pkgData;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Formats an array of strings to a string of quoted, comma separated values.
|
|
91
|
+
* @param list The list of unformatted strings to format
|
|
92
|
+
* @returns The formatted array of strings. (e.g. ['foo', 'bar']) => `'foo', 'bar'`
|
|
93
|
+
*/
|
|
94
|
+
const formatToQuotedList = (list) => list.map((item) => `'${item}'`).join(', ');
|
|
95
|
+
/**
|
|
96
|
+
* Creates an import statement for a list of named imports from a module.
|
|
97
|
+
* @param imports The list of named imports.
|
|
98
|
+
* @param module The module to import from.
|
|
99
|
+
*
|
|
100
|
+
* @returns The import statement as a string.
|
|
101
|
+
*/
|
|
102
|
+
const createImportStatement = (imports, module) => {
|
|
103
|
+
if (imports.length === 0) {
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
106
|
+
return `import { ${imports.join(', ')} } from '${module}';`;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Checks if the outputType is for the custom elements build.
|
|
110
|
+
* @param outputType The output type.
|
|
111
|
+
* @returns `true` if the output type is for the custom elements build.
|
|
112
|
+
*/
|
|
113
|
+
const isOutputTypeCustomElementsBuild = (outputType) => {
|
|
114
|
+
return outputType === OutputTypes.Standalone || outputType === OutputTypes.Scam;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Creates the collection of import statements for a component based on the component's events type dependencies.
|
|
118
|
+
* @param componentTagName The tag name of the component (pascal case).
|
|
119
|
+
* @param events The events compiler metadata.
|
|
120
|
+
* @param options The options for generating the import statements (e.g. whether to import from the custom elements directory).
|
|
121
|
+
* @returns The import statements as an array of strings.
|
|
122
|
+
*/
|
|
123
|
+
const createComponentEventTypeImports = (componentTagName, events, options) => {
|
|
124
|
+
const { componentCorePackage, customElementsDir } = options;
|
|
125
|
+
const imports = [];
|
|
126
|
+
const namedImports = new Set();
|
|
127
|
+
const isCustomElementsBuild = isOutputTypeCustomElementsBuild(options.outputType);
|
|
128
|
+
const importPathName = normalizePath(componentCorePackage) + (isCustomElementsBuild ? `/${customElementsDir}` : '');
|
|
129
|
+
events.forEach((event) => {
|
|
130
|
+
Object.entries(event.complexType.references).forEach(([typeName, refObject]) => {
|
|
131
|
+
if (refObject.location === 'local' || refObject.location === 'import') {
|
|
132
|
+
const newTypeName = `I${componentTagName}${typeName}`;
|
|
133
|
+
// Prevents duplicate imports for the same type.
|
|
134
|
+
if (!namedImports.has(newTypeName)) {
|
|
135
|
+
imports.push(`import type { ${typeName} as ${newTypeName} } from '${importPathName}';`);
|
|
136
|
+
namedImports.add(newTypeName);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
return imports.join('\n');
|
|
142
|
+
};
|
|
143
|
+
const EXTENDED_PATH_REGEX = /^\\\\\?\\/;
|
|
144
|
+
const NON_ASCII_REGEX = /[^\x00-\x80]+/;
|
|
145
|
+
const SLASH_REGEX = /\\/g;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Creates an Angular component declaration from formatted Stencil compiler metadata.
|
|
149
|
+
*
|
|
150
|
+
* @param tagName The tag name of the component.
|
|
151
|
+
* @param inputs The inputs of the Stencil component (e.g. ['myInput']).
|
|
152
|
+
* @param outputs The outputs/events of the Stencil component. (e.g. ['myOutput']).
|
|
153
|
+
* @param methods The methods of the Stencil component. (e.g. ['myMethod']).
|
|
154
|
+
* @param includeImportCustomElements Whether to define the component as a custom element.
|
|
155
|
+
* @param standalone Whether to define the component as a standalone component.
|
|
156
|
+
* @returns The component declaration as a string.
|
|
157
|
+
*/
|
|
158
|
+
const createAngularComponentDefinition = (tagName, inputs, outputs, methods, includeImportCustomElements = false, standalone = false) => {
|
|
159
|
+
const tagNameAsPascal = dashToPascalCase(tagName);
|
|
160
|
+
const hasInputs = inputs.length > 0;
|
|
161
|
+
const hasOutputs = outputs.length > 0;
|
|
162
|
+
const hasMethods = methods.length > 0;
|
|
163
|
+
// Formats the input strings into comma separated, single quoted values.
|
|
164
|
+
const formattedInputs = formatToQuotedList(inputs);
|
|
165
|
+
// Formats the output strings into comma separated, single quoted values.
|
|
166
|
+
const formattedOutputs = formatToQuotedList(outputs);
|
|
167
|
+
// Formats the method strings into comma separated, single quoted values.
|
|
168
|
+
const formattedMethods = formatToQuotedList(methods);
|
|
169
|
+
const proxyCmpOptions = [];
|
|
170
|
+
if (includeImportCustomElements) {
|
|
171
|
+
const defineCustomElementFn = `define${tagNameAsPascal}`;
|
|
172
|
+
proxyCmpOptions.push(`\n defineCustomElementFn: ${defineCustomElementFn}`);
|
|
173
|
+
}
|
|
174
|
+
if (hasInputs) {
|
|
175
|
+
proxyCmpOptions.push(`\n inputs: [${formattedInputs}]`);
|
|
176
|
+
}
|
|
177
|
+
if (hasMethods) {
|
|
178
|
+
proxyCmpOptions.push(`\n methods: [${formattedMethods}]`);
|
|
179
|
+
}
|
|
180
|
+
let standaloneOption = '';
|
|
181
|
+
if (standalone && includeImportCustomElements) {
|
|
182
|
+
standaloneOption = `\n standalone: true`;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Notes on the generated output:
|
|
186
|
+
* - We disable @angular-eslint/no-inputs-metadata-property, so that
|
|
187
|
+
* Angular does not complain about the inputs property. The output target
|
|
188
|
+
* uses the inputs property to define the inputs of the component instead of
|
|
189
|
+
* having to use the @Input decorator (and manually define the type and default value).
|
|
190
|
+
*/
|
|
191
|
+
const output = `@ProxyCmp({${proxyCmpOptions.join(',')}\n})
|
|
192
|
+
@Component({
|
|
193
|
+
selector: '${tagName}',
|
|
194
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
195
|
+
template: '<ng-content></ng-content>',
|
|
196
|
+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
|
197
|
+
inputs: [${formattedInputs}],${standaloneOption}
|
|
198
|
+
})
|
|
199
|
+
export class ${tagNameAsPascal} {
|
|
200
|
+
protected el: HTMLElement;
|
|
201
|
+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
|
202
|
+
c.detach();
|
|
203
|
+
this.el = r.nativeElement;${hasOutputs
|
|
204
|
+
? `
|
|
205
|
+
proxyOutputs(this, this.el, [${formattedOutputs}]);`
|
|
206
|
+
: ''}
|
|
207
|
+
}
|
|
208
|
+
}`;
|
|
209
|
+
return output;
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Sanitizes and formats the component event type.
|
|
213
|
+
* @param componentClassName The class name of the component (e.g. 'MyComponent')
|
|
214
|
+
* @param event The Stencil component event.
|
|
215
|
+
* @returns The sanitized event type as a string.
|
|
216
|
+
*/
|
|
217
|
+
const formatOutputType = (componentClassName, event) => {
|
|
218
|
+
const prefix = `I${componentClassName}`;
|
|
219
|
+
/**
|
|
220
|
+
* The original attribute contains the original type defined by the devs.
|
|
221
|
+
* This regexp normalizes the reference, by removing linebreaks,
|
|
222
|
+
* replacing consecutive spaces with a single space, and adding a single space after commas.
|
|
223
|
+
*/
|
|
224
|
+
return Object.entries(event.complexType.references)
|
|
225
|
+
.filter(([_, refObject]) => refObject.location === 'local' || refObject.location === 'import')
|
|
226
|
+
.reduce((type, [src, dst]) => {
|
|
227
|
+
let renamedType = type;
|
|
228
|
+
if (!type.startsWith(prefix)) {
|
|
229
|
+
renamedType = `I${componentClassName}${type}`;
|
|
230
|
+
}
|
|
231
|
+
return (renamedType
|
|
232
|
+
.replace(new RegExp(`^${src}$`, 'g'), `${dst}`)
|
|
233
|
+
// Capture all instances of the `src` field surrounded by non-word characters on each side and join them.
|
|
234
|
+
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (v, p1, p2) => {
|
|
235
|
+
if ((dst === null || dst === void 0 ? void 0 : dst.location) === 'import') {
|
|
236
|
+
/**
|
|
237
|
+
* Replaces a complex type reference within a generic type.
|
|
238
|
+
* For example, remapping a type like `EventEmitter<CustomEvent<MyEvent<T>>>` to
|
|
239
|
+
* `EventEmitter<CustomEvent<IMyComponentMyEvent<IMyComponentT>>>`.
|
|
240
|
+
*/
|
|
241
|
+
return [p1, `I${componentClassName}${v.substring(1, v.length - 1)}`, p2].join('');
|
|
242
|
+
}
|
|
243
|
+
return [p1, dst, p2].join('');
|
|
244
|
+
}));
|
|
245
|
+
}, event.complexType.original
|
|
246
|
+
.replace(/\n/g, ' ')
|
|
247
|
+
.replace(/\s{2,}/g, ' ')
|
|
248
|
+
.replace(/,\s*/g, ', '));
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* Creates a formatted comment block based on the JS doc comment.
|
|
252
|
+
* @param doc The compiler jsdoc.
|
|
253
|
+
* @returns The formatted comment block as a string.
|
|
254
|
+
*/
|
|
255
|
+
const createDocComment = (doc) => {
|
|
256
|
+
if (doc.text.trim().length === 0 && doc.tags.length === 0) {
|
|
257
|
+
return '';
|
|
258
|
+
}
|
|
259
|
+
return `/**
|
|
260
|
+
* ${doc.text}${doc.tags.length > 0 ? ' ' : ''}${doc.tags.map((tag) => `@${tag.name} ${tag.text}`)}
|
|
261
|
+
*/`;
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* Creates the component interface type definition.
|
|
265
|
+
* @param outputType The output type.
|
|
266
|
+
* @param tagNameAsPascal The tag name as PascalCase.
|
|
267
|
+
* @param events The events to generate the interface properties for.
|
|
268
|
+
* @param componentCorePackage The component core package.
|
|
269
|
+
* @param customElementsDir The custom elements directory.
|
|
270
|
+
* @returns The component interface type definition as a string.
|
|
271
|
+
*/
|
|
272
|
+
const createComponentTypeDefinition = (outputType, tagNameAsPascal, events, componentCorePackage, customElementsDir) => {
|
|
273
|
+
const publicEvents = events.filter((ev) => !ev.internal);
|
|
274
|
+
const eventTypeImports = createComponentEventTypeImports(tagNameAsPascal, publicEvents, {
|
|
275
|
+
componentCorePackage,
|
|
276
|
+
customElementsDir,
|
|
277
|
+
outputType,
|
|
278
|
+
});
|
|
279
|
+
const eventTypes = publicEvents.map((event) => {
|
|
280
|
+
const comment = createDocComment(event.docs);
|
|
281
|
+
let eventName = event.name;
|
|
282
|
+
if (event.name.includes('-')) {
|
|
283
|
+
// If an event name includes a dash, we need to wrap it in quotes.
|
|
284
|
+
// https://github.com/ionic-team/stencil-ds-output-targets/issues/212
|
|
285
|
+
eventName = `'${event.name}'`;
|
|
286
|
+
}
|
|
287
|
+
return `${comment.length > 0 ? ` ${comment}` : ''}
|
|
288
|
+
${eventName}: EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>;`;
|
|
289
|
+
});
|
|
290
|
+
const interfaceDeclaration = `export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {`;
|
|
291
|
+
const typeDefinition = (eventTypeImports.length > 0 ? `${eventTypeImports + '\n\n'}` : '') +
|
|
292
|
+
`${interfaceDeclaration}${eventTypes.length === 0
|
|
293
|
+
? '}'
|
|
294
|
+
: `
|
|
295
|
+
${eventTypes.join('\n')}
|
|
296
|
+
}`}`;
|
|
297
|
+
return typeDefinition;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
function generateAngularDirectivesFile(compilerCtx, components, outputTarget) {
|
|
301
|
+
// Only create the file if it is defined in the stencil configuration
|
|
302
|
+
if (!outputTarget.directivesArrayFile) {
|
|
303
|
+
return Promise.resolve();
|
|
304
|
+
}
|
|
305
|
+
const proxyPath = relativeImport(outputTarget.directivesArrayFile, outputTarget.directivesProxyFile, '.ts');
|
|
306
|
+
const directives = components
|
|
307
|
+
.map((cmpMeta) => dashToPascalCase(cmpMeta.tagName))
|
|
308
|
+
.map((className) => `d.${className}`)
|
|
309
|
+
.join(',\n ');
|
|
310
|
+
const c = `
|
|
311
|
+
import * as d from '${proxyPath}';
|
|
312
|
+
|
|
313
|
+
export const DIRECTIVES = [
|
|
314
|
+
${directives}
|
|
315
|
+
];
|
|
316
|
+
`;
|
|
317
|
+
return compilerCtx.fs.writeFile(outputTarget.directivesArrayFile, c);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function generateValueAccessors(compilerCtx, components, outputTarget, config) {
|
|
321
|
+
if (!Array.isArray(outputTarget.valueAccessorConfigs) || outputTarget.valueAccessorConfigs.length === 0) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const targetDir = path__default['default'].dirname(outputTarget.directivesProxyFile);
|
|
325
|
+
const normalizedValueAccessors = outputTarget.valueAccessorConfigs.reduce((allAccessors, va) => {
|
|
326
|
+
const elementSelectors = Array.isArray(va.elementSelectors) ? va.elementSelectors : [va.elementSelectors];
|
|
327
|
+
const type = va.type;
|
|
328
|
+
let allElementSelectors = [];
|
|
329
|
+
let allEventTargets = [];
|
|
330
|
+
if (allAccessors.hasOwnProperty(type)) {
|
|
331
|
+
allElementSelectors = allAccessors[type].elementSelectors;
|
|
332
|
+
allEventTargets = allAccessors[type].eventTargets;
|
|
333
|
+
}
|
|
334
|
+
return Object.assign(Object.assign({}, allAccessors), { [type]: {
|
|
335
|
+
elementSelectors: allElementSelectors.concat(elementSelectors),
|
|
336
|
+
eventTargets: allEventTargets.concat([[va.event, va.targetAttr]]),
|
|
337
|
+
} });
|
|
338
|
+
}, {});
|
|
339
|
+
await Promise.all(Object.keys(normalizedValueAccessors).map(async (type) => {
|
|
340
|
+
const valueAccessorType = type; // Object.keys converts to string
|
|
341
|
+
const targetFileName = `${type}-value-accessor.ts`;
|
|
342
|
+
const targetFilePath = path__default['default'].join(targetDir, targetFileName);
|
|
343
|
+
const srcFilePath = path__default['default'].join(__dirname, '../resources/control-value-accessors/', targetFileName);
|
|
344
|
+
const srcFileContents = await compilerCtx.fs.readFile(srcFilePath);
|
|
345
|
+
const finalText = createValueAccessor(srcFileContents, normalizedValueAccessors[valueAccessorType]);
|
|
346
|
+
await compilerCtx.fs.writeFile(targetFilePath, finalText);
|
|
347
|
+
}));
|
|
348
|
+
await copyResources(config, ['value-accessor.ts'], targetDir);
|
|
349
|
+
}
|
|
350
|
+
function createValueAccessor(srcFileContents, valueAccessor) {
|
|
351
|
+
const hostContents = valueAccessor.eventTargets.map((listItem) => VALUE_ACCESSOR_EVENTTARGETS.replace(VALUE_ACCESSOR_EVENT, listItem[0]).replace(VALUE_ACCESSOR_TARGETATTR, listItem[1]));
|
|
352
|
+
return srcFileContents
|
|
353
|
+
.replace(VALUE_ACCESSOR_SELECTORS, valueAccessor.elementSelectors.join(', '))
|
|
354
|
+
.replace(VALUE_ACCESSOR_EVENTTARGETS, hostContents.join(`,${os.EOL}`));
|
|
355
|
+
}
|
|
356
|
+
function copyResources(config, resourcesFilesToCopy, directory) {
|
|
357
|
+
if (!config.sys || !config.sys.copy) {
|
|
358
|
+
throw new Error('stencil is not properly intialized at this step. Notify the developer');
|
|
359
|
+
}
|
|
360
|
+
const copyTasks = resourcesFilesToCopy.map((rf) => {
|
|
361
|
+
return {
|
|
362
|
+
src: path__default['default'].join(__dirname, '../resources/control-value-accessors/', rf),
|
|
363
|
+
dest: path__default['default'].join(directory, rf),
|
|
364
|
+
keepDirStructure: false,
|
|
365
|
+
warn: false,
|
|
366
|
+
};
|
|
367
|
+
});
|
|
368
|
+
return config.sys.copy(copyTasks, path__default['default'].join(directory));
|
|
369
|
+
}
|
|
370
|
+
const VALUE_ACCESSOR_SELECTORS = `<VALUE_ACCESSOR_SELECTORS>`;
|
|
371
|
+
const VALUE_ACCESSOR_EVENT = `<VALUE_ACCESSOR_EVENT>`;
|
|
372
|
+
const VALUE_ACCESSOR_TARGETATTR = '<VALUE_ACCESSOR_TARGETATTR>';
|
|
373
|
+
const VALUE_ACCESSOR_EVENTTARGETS = ` '(<VALUE_ACCESSOR_EVENT>)': 'handleChangeEvent($event.target.<VALUE_ACCESSOR_TARGETATTR>)'`;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Creates an Angular module declaration for a component wrapper.
|
|
377
|
+
* @param componentTagName The tag name of the Stencil component.
|
|
378
|
+
* @returns The Angular module declaration as a string.
|
|
379
|
+
*/
|
|
380
|
+
const generateAngularModuleForComponent = (componentTagName) => {
|
|
381
|
+
const tagNameAsPascal = dashToPascalCase(componentTagName);
|
|
382
|
+
const componentClassName = `${tagNameAsPascal}`;
|
|
383
|
+
const moduleClassName = `${tagNameAsPascal}Module`;
|
|
384
|
+
const moduleDefinition = `@NgModule({
|
|
385
|
+
declarations: [${componentClassName}],
|
|
386
|
+
exports: [${componentClassName}]
|
|
387
|
+
})
|
|
388
|
+
export class ${moduleClassName} { }`;
|
|
389
|
+
return moduleDefinition;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
|
|
393
|
+
const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
|
|
394
|
+
const rootDir = config.rootDir;
|
|
395
|
+
const pkgData = await readPackageJson(config, rootDir);
|
|
396
|
+
const finalText = generateProxies(filteredComponents, pkgData, outputTarget, config.rootDir);
|
|
397
|
+
await Promise.all([
|
|
398
|
+
compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText),
|
|
399
|
+
copyResources$1(config, outputTarget),
|
|
400
|
+
generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget),
|
|
401
|
+
generateValueAccessors(compilerCtx, filteredComponents, outputTarget, config),
|
|
402
|
+
]);
|
|
403
|
+
}
|
|
404
|
+
function getFilteredComponents(excludeComponents = [], cmps) {
|
|
405
|
+
return sortBy(cmps, (cmp) => cmp.tagName).filter((c) => !excludeComponents.includes(c.tagName) && !c.internal);
|
|
406
|
+
}
|
|
407
|
+
async function copyResources$1(config, outputTarget) {
|
|
408
|
+
if (!config.sys || !config.sys.copy || !config.sys.glob) {
|
|
409
|
+
throw new Error('stencil is not properly initialized at this step. Notify the developer');
|
|
410
|
+
}
|
|
411
|
+
const srcDirectory = path__default['default'].join(__dirname, '..', 'angular-component-lib');
|
|
412
|
+
const destDirectory = path__default['default'].join(path__default['default'].dirname(outputTarget.directivesProxyFile), 'angular-component-lib');
|
|
413
|
+
return config.sys.copy([
|
|
414
|
+
{
|
|
415
|
+
src: srcDirectory,
|
|
416
|
+
dest: destDirectory,
|
|
417
|
+
keepDirStructure: false,
|
|
418
|
+
warn: false,
|
|
419
|
+
},
|
|
420
|
+
], srcDirectory);
|
|
421
|
+
}
|
|
422
|
+
function generateProxies(components, pkgData, outputTarget, rootDir) {
|
|
423
|
+
const distTypesDir = path__default['default'].dirname(pkgData.types);
|
|
424
|
+
const dtsFilePath = path__default['default'].join(rootDir, distTypesDir, GENERATED_DTS);
|
|
425
|
+
const { outputType } = outputTarget;
|
|
426
|
+
const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
|
|
427
|
+
const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
|
|
428
|
+
const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputType);
|
|
429
|
+
const isStandaloneBuild = outputType === OutputTypes.Standalone;
|
|
430
|
+
const includeOutputImports = components.some((component) => component.events.some((event) => !event.internal));
|
|
431
|
+
/**
|
|
432
|
+
* The collection of named imports from @angular/core.
|
|
433
|
+
*/
|
|
434
|
+
const angularCoreImports = ['ChangeDetectionStrategy', 'ChangeDetectorRef', 'Component', 'ElementRef'];
|
|
435
|
+
if (includeOutputImports) {
|
|
436
|
+
angularCoreImports.push('EventEmitter');
|
|
437
|
+
}
|
|
438
|
+
angularCoreImports.push('NgZone');
|
|
439
|
+
/**
|
|
440
|
+
* The collection of named imports from the angular-component-lib/utils.
|
|
441
|
+
*/
|
|
442
|
+
const componentLibImports = ['ProxyCmp'];
|
|
443
|
+
if (includeOutputImports) {
|
|
444
|
+
componentLibImports.push('proxyOutputs');
|
|
445
|
+
}
|
|
446
|
+
if (includeSingleComponentAngularModules) {
|
|
447
|
+
angularCoreImports.push('NgModule');
|
|
448
|
+
}
|
|
449
|
+
const imports = `/* tslint:disable */
|
|
450
|
+
/* auto-generated angular directive proxies */
|
|
451
|
+
${createImportStatement(angularCoreImports, '@angular/core')}
|
|
452
|
+
|
|
453
|
+
${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`;
|
|
454
|
+
/**
|
|
455
|
+
* Generate JSX import type from correct location.
|
|
456
|
+
* When using custom elements build, we need to import from
|
|
457
|
+
* either the "components" directory or customElementsDir
|
|
458
|
+
* otherwise we risk bundlers pulling in lazy loaded imports.
|
|
459
|
+
*/
|
|
460
|
+
const generateTypeImports = () => {
|
|
461
|
+
let importLocation = outputTarget.componentCorePackage
|
|
462
|
+
? normalizePath(outputTarget.componentCorePackage)
|
|
463
|
+
: normalizePath(componentsTypeFile);
|
|
464
|
+
importLocation += isCustomElementsBuild ? `/${outputTarget.customElementsDir}` : '';
|
|
465
|
+
return `import ${isCustomElementsBuild ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
|
|
466
|
+
};
|
|
467
|
+
const typeImports = generateTypeImports();
|
|
468
|
+
let sourceImports = '';
|
|
469
|
+
/**
|
|
470
|
+
* Build an array of Custom Elements build imports and namespace them
|
|
471
|
+
* so that they do not conflict with the Angular wrapper names. For example,
|
|
472
|
+
* IonButton would be imported as IonButtonCmp so as to not conflict with the
|
|
473
|
+
* IonButton Angular Component that takes in the Web Component as a parameter.
|
|
474
|
+
*/
|
|
475
|
+
if (isCustomElementsBuild && outputTarget.componentCorePackage !== undefined) {
|
|
476
|
+
const cmpImports = components.map((component) => {
|
|
477
|
+
const pascalImport = dashToPascalCase(component.tagName);
|
|
478
|
+
return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir}/${component.tagName}.js';`;
|
|
479
|
+
});
|
|
480
|
+
sourceImports = cmpImports.join('\n');
|
|
481
|
+
}
|
|
482
|
+
const proxyFileOutput = [];
|
|
483
|
+
const filterInternalProps = (prop) => !prop.internal;
|
|
484
|
+
const mapPropName = (prop) => prop.name;
|
|
485
|
+
const { componentCorePackage, customElementsDir } = outputTarget;
|
|
486
|
+
for (let cmpMeta of components) {
|
|
487
|
+
const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
|
|
488
|
+
const inputs = [];
|
|
489
|
+
if (cmpMeta.properties) {
|
|
490
|
+
inputs.push(...cmpMeta.properties.filter(filterInternalProps).map(mapPropName));
|
|
491
|
+
}
|
|
492
|
+
if (cmpMeta.virtualProperties) {
|
|
493
|
+
inputs.push(...cmpMeta.virtualProperties.map(mapPropName));
|
|
494
|
+
}
|
|
495
|
+
inputs.sort();
|
|
496
|
+
const outputs = [];
|
|
497
|
+
if (cmpMeta.events) {
|
|
498
|
+
outputs.push(...cmpMeta.events.filter(filterInternalProps).map(mapPropName));
|
|
499
|
+
}
|
|
500
|
+
const methods = [];
|
|
501
|
+
if (cmpMeta.methods) {
|
|
502
|
+
methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* For each component, we need to generate:
|
|
506
|
+
* 1. The @Component decorated class
|
|
507
|
+
* 2. Optionally the @NgModule decorated class (if includeSingleComponentAngularModules is true)
|
|
508
|
+
* 3. The component interface (using declaration merging for types).
|
|
509
|
+
*/
|
|
510
|
+
const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, isCustomElementsBuild, isStandaloneBuild);
|
|
511
|
+
const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
|
|
512
|
+
const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
|
|
513
|
+
proxyFileOutput.push(componentDefinition, '\n');
|
|
514
|
+
if (includeSingleComponentAngularModules) {
|
|
515
|
+
proxyFileOutput.push(moduleDefinition, '\n');
|
|
516
|
+
}
|
|
517
|
+
proxyFileOutput.push(componentTypeDefinition, '\n');
|
|
518
|
+
}
|
|
519
|
+
const final = [imports, typeImports, sourceImports, ...proxyFileOutput];
|
|
520
|
+
return final.join('\n') + '\n';
|
|
521
|
+
}
|
|
522
|
+
const GENERATED_DTS = 'components.d.ts';
|
|
523
|
+
const IMPORT_TYPES = 'Components';
|
|
524
|
+
|
|
525
|
+
const angularOutputTarget = (outputTarget) => {
|
|
526
|
+
let validatedOutputTarget;
|
|
527
|
+
return {
|
|
528
|
+
type: 'custom',
|
|
529
|
+
name: 'angular-library',
|
|
530
|
+
validate(config) {
|
|
531
|
+
validatedOutputTarget = normalizeOutputTarget(config, outputTarget);
|
|
532
|
+
},
|
|
533
|
+
async generator(config, compilerCtx, buildCtx) {
|
|
534
|
+
const timespan = buildCtx.createTimeSpan(`generate angular proxies started`, true);
|
|
535
|
+
await angularDirectiveProxyOutput(compilerCtx, validatedOutputTarget, buildCtx.components, config);
|
|
536
|
+
timespan.finish(`generate angular proxies finished`);
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
};
|
|
540
|
+
function normalizeOutputTarget(config, outputTarget) {
|
|
541
|
+
const results = Object.assign(Object.assign({}, outputTarget), { excludeComponents: outputTarget.excludeComponents || [], valueAccessorConfigs: outputTarget.valueAccessorConfigs || [], customElementsDir: outputTarget.customElementsDir || 'components', outputType: outputTarget.outputType || OutputTypes.Component });
|
|
542
|
+
if (config.rootDir == null) {
|
|
543
|
+
throw new Error('rootDir is not set and it should be set by stencil itself');
|
|
544
|
+
}
|
|
545
|
+
if (outputTarget.directivesProxyFile == null) {
|
|
546
|
+
throw new Error('directivesProxyFile is required. Please set it in the Stencil config.');
|
|
547
|
+
}
|
|
548
|
+
if (outputTarget.directivesProxyFile && !path__default['default'].isAbsolute(outputTarget.directivesProxyFile)) {
|
|
549
|
+
results.directivesProxyFile = normalizePath(path__default['default'].join(config.rootDir, outputTarget.directivesProxyFile));
|
|
550
|
+
}
|
|
551
|
+
if (outputTarget.directivesArrayFile && !path__default['default'].isAbsolute(outputTarget.directivesArrayFile)) {
|
|
552
|
+
results.directivesArrayFile = normalizePath(path__default['default'].join(config.rootDir, outputTarget.directivesArrayFile));
|
|
553
|
+
}
|
|
554
|
+
if (outputTarget.includeSingleComponentAngularModules !== undefined) {
|
|
555
|
+
throw new Error("The 'includeSingleComponentAngularModules' option has been removed. Please use 'outputType' instead.");
|
|
556
|
+
}
|
|
557
|
+
return results;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
exports.angularOutputTarget = angularOutputTarget;
|
package/dist/index.d.ts
ADDED