@stencil/angular-output-target 1.2.0 → 1.3.1
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/dist/generate-angular-component.js +15 -15
- package/dist/index.cjs.js +128 -18
- package/dist/index.js +128 -18
- package/dist/output-angular.d.ts +8 -0
- package/dist/output-angular.js +113 -3
- package/dist/types.d.ts +31 -6
- package/package.json +1 -1
|
@@ -89,7 +89,7 @@ export const createAngularComponentDefinition = (tagName, inputs, methods, inclu
|
|
|
89
89
|
const outputDeclarations = events
|
|
90
90
|
.filter((event) => !event.internal)
|
|
91
91
|
.map((event) => {
|
|
92
|
-
const camelCaseOutput = event.name.replace(/-([a-z])/g, (
|
|
92
|
+
const camelCaseOutput = event.name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
93
93
|
const outputType = `EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>`;
|
|
94
94
|
return `@Output() ${camelCaseOutput} = new ${outputType}();`;
|
|
95
95
|
});
|
|
@@ -137,7 +137,7 @@ const formatOutputType = (componentClassName, event) => {
|
|
|
137
137
|
*/
|
|
138
138
|
return Object.entries(event.complexType.references)
|
|
139
139
|
.filter(([_, refObject]) => refObject.location === 'local' || refObject.location === 'import')
|
|
140
|
-
.reduce((type, [src
|
|
140
|
+
.reduce((type, [src]) => {
|
|
141
141
|
let renamedType = type;
|
|
142
142
|
if (!type.startsWith(prefix)) {
|
|
143
143
|
if (type.startsWith('{') && type.endsWith('}')) {
|
|
@@ -157,28 +157,28 @@ const formatOutputType = (componentClassName, event) => {
|
|
|
157
157
|
renamedType = `I${componentClassName}${type}`;
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
|
+
const prefixedTypeName = `${prefix}${src}`;
|
|
160
161
|
return (renamedType
|
|
161
|
-
.replace(new RegExp(`^${src}$`, 'g'),
|
|
162
|
+
.replace(new RegExp(`^${src}$`, 'g'), prefixedTypeName)
|
|
162
163
|
// Capture all instances of the `src` field surrounded by non-word characters on each side and join them.
|
|
163
|
-
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return [p1, `I${componentClassName}${v.substring(1, v.length - 1)}`, p2].join('');
|
|
171
|
-
}
|
|
172
|
-
return [p1, dst, p2].join('');
|
|
164
|
+
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (_, p1, p2) => {
|
|
165
|
+
/**
|
|
166
|
+
* Replaces a complex type reference within a generic type.
|
|
167
|
+
* For example, remapping a type like `EventEmitter<CustomEvent<MyEvent<T>>>` to
|
|
168
|
+
* `EventEmitter<CustomEvent<IMyComponentMyEvent<IMyComponentT>>>`.
|
|
169
|
+
*/
|
|
170
|
+
return [p1, prefixedTypeName, p2].join('');
|
|
173
171
|
})
|
|
174
172
|
// Capture all instances that contain sub types, e.g. `IMyComponent.SomeMoreComplexType.SubType`.
|
|
175
173
|
.replace(new RegExp(`^${src}(\.\\w+)+$`, 'g'), (type) => {
|
|
176
|
-
return
|
|
174
|
+
return `${prefix}${src}.${type.split('.').slice(1).join('.')}`;
|
|
177
175
|
}));
|
|
178
176
|
}, event.complexType.original
|
|
177
|
+
.replace(/\/\/.*$/gm, '') // Remove single-line comments before collapsing newlines
|
|
179
178
|
.replace(/\n/g, ' ')
|
|
180
179
|
.replace(/\s{2,}/g, ' ')
|
|
181
|
-
.replace(/,\s*/g, ', ')
|
|
180
|
+
.replace(/,\s*/g, ', ')
|
|
181
|
+
.trim());
|
|
182
182
|
};
|
|
183
183
|
/**
|
|
184
184
|
* Creates a formatted comment block based on the JS doc comment.
|
package/dist/index.cjs.js
CHANGED
|
@@ -235,7 +235,7 @@ const createAngularComponentDefinition = (tagName, inputs, methods, includeImpor
|
|
|
235
235
|
const outputDeclarations = events
|
|
236
236
|
.filter((event) => !event.internal)
|
|
237
237
|
.map((event) => {
|
|
238
|
-
const camelCaseOutput = event.name.replace(/-([a-z])/g, (
|
|
238
|
+
const camelCaseOutput = event.name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
239
239
|
const outputType = `EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>`;
|
|
240
240
|
return `@Output() ${camelCaseOutput} = new ${outputType}();`;
|
|
241
241
|
});
|
|
@@ -283,7 +283,7 @@ const formatOutputType = (componentClassName, event) => {
|
|
|
283
283
|
*/
|
|
284
284
|
return Object.entries(event.complexType.references)
|
|
285
285
|
.filter(([_, refObject]) => refObject.location === 'local' || refObject.location === 'import')
|
|
286
|
-
.reduce((type, [src
|
|
286
|
+
.reduce((type, [src]) => {
|
|
287
287
|
let renamedType = type;
|
|
288
288
|
if (!type.startsWith(prefix)) {
|
|
289
289
|
if (type.startsWith('{') && type.endsWith('}')) {
|
|
@@ -303,28 +303,28 @@ const formatOutputType = (componentClassName, event) => {
|
|
|
303
303
|
renamedType = `I${componentClassName}${type}`;
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
|
+
const prefixedTypeName = `${prefix}${src}`;
|
|
306
307
|
return (renamedType
|
|
307
|
-
.replace(new RegExp(`^${src}$`, 'g'),
|
|
308
|
+
.replace(new RegExp(`^${src}$`, 'g'), prefixedTypeName)
|
|
308
309
|
// Capture all instances of the `src` field surrounded by non-word characters on each side and join them.
|
|
309
|
-
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
return [p1, `I${componentClassName}${v.substring(1, v.length - 1)}`, p2].join('');
|
|
317
|
-
}
|
|
318
|
-
return [p1, dst, p2].join('');
|
|
310
|
+
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (_, p1, p2) => {
|
|
311
|
+
/**
|
|
312
|
+
* Replaces a complex type reference within a generic type.
|
|
313
|
+
* For example, remapping a type like `EventEmitter<CustomEvent<MyEvent<T>>>` to
|
|
314
|
+
* `EventEmitter<CustomEvent<IMyComponentMyEvent<IMyComponentT>>>`.
|
|
315
|
+
*/
|
|
316
|
+
return [p1, prefixedTypeName, p2].join('');
|
|
319
317
|
})
|
|
320
318
|
// Capture all instances that contain sub types, e.g. `IMyComponent.SomeMoreComplexType.SubType`.
|
|
321
319
|
.replace(new RegExp(`^${src}(\.\\w+)+$`, 'g'), (type) => {
|
|
322
|
-
return
|
|
320
|
+
return `${prefix}${src}.${type.split('.').slice(1).join('.')}`;
|
|
323
321
|
}));
|
|
324
322
|
}, event.complexType.original
|
|
323
|
+
.replace(/\/\/.*$/gm, '') // Remove single-line comments before collapsing newlines
|
|
325
324
|
.replace(/\n/g, ' ')
|
|
326
325
|
.replace(/\s{2,}/g, ' ')
|
|
327
|
-
.replace(/,\s*/g, ', ')
|
|
326
|
+
.replace(/,\s*/g, ', ')
|
|
327
|
+
.trim());
|
|
328
328
|
};
|
|
329
329
|
/**
|
|
330
330
|
* Creates a formatted comment block based on the JS doc comment.
|
|
@@ -771,13 +771,33 @@ async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components
|
|
|
771
771
|
const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
|
|
772
772
|
const rootDir = config.rootDir;
|
|
773
773
|
const pkgData = await readPackageJson(config, rootDir);
|
|
774
|
-
|
|
774
|
+
// esModules defaults to true, but only applies when outputType is 'scam' or 'standalone'
|
|
775
|
+
const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputTarget.outputType);
|
|
776
|
+
const useEsModules = isCustomElementsBuild && outputTarget.esModules === true;
|
|
775
777
|
const tasks = [
|
|
776
|
-
compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText),
|
|
777
778
|
copyResources(config, outputTarget),
|
|
778
|
-
generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget),
|
|
779
779
|
generateValueAccessors(compilerCtx, filteredComponents, outputTarget, config),
|
|
780
780
|
];
|
|
781
|
+
if (useEsModules) {
|
|
782
|
+
// Generate separate files for each component
|
|
783
|
+
const proxiesDir = path__default["default"].dirname(outputTarget.directivesProxyFile);
|
|
784
|
+
for (const component of filteredComponents) {
|
|
785
|
+
const componentFile = path__default["default"].join(proxiesDir, `${component.tagName}.ts`);
|
|
786
|
+
const componentText = generateComponentProxy(component, pkgData, outputTarget, rootDir);
|
|
787
|
+
tasks.push(compilerCtx.fs.writeFile(componentFile, componentText));
|
|
788
|
+
}
|
|
789
|
+
// Generate barrel file that re-exports all components
|
|
790
|
+
const barrelText = generateBarrelFile(filteredComponents, outputTarget);
|
|
791
|
+
tasks.push(compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, barrelText));
|
|
792
|
+
// Generate DIRECTIVES file (imports from barrel)
|
|
793
|
+
tasks.push(generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget));
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
// Generate single file with all components (original behavior)
|
|
797
|
+
const finalText = generateProxies(filteredComponents, pkgData, outputTarget, rootDir);
|
|
798
|
+
tasks.push(compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText));
|
|
799
|
+
tasks.push(generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget));
|
|
800
|
+
}
|
|
781
801
|
// Generate transformer script if transformTag is enabled
|
|
782
802
|
if (outputTarget.transformTag) {
|
|
783
803
|
// Read the Angular library's package.json to get its name
|
|
@@ -925,6 +945,96 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
|
|
|
925
945
|
const final = [imports, typeImports, sourceImports, ...proxyFileOutput];
|
|
926
946
|
return final.join('\n') + '\n';
|
|
927
947
|
}
|
|
948
|
+
/**
|
|
949
|
+
* Generate a single component proxy file for ES modules output
|
|
950
|
+
*/
|
|
951
|
+
function generateComponentProxy(cmpMeta, pkgData, outputTarget, rootDir) {
|
|
952
|
+
var _a;
|
|
953
|
+
const { outputType, componentCorePackage, customElementsDir } = outputTarget;
|
|
954
|
+
const distTypesDir = path__default["default"].dirname(pkgData.types);
|
|
955
|
+
const dtsFilePath = path__default["default"].join(rootDir, distTypesDir, GENERATED_DTS);
|
|
956
|
+
const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
|
|
957
|
+
const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
|
|
958
|
+
const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputType);
|
|
959
|
+
const isStandaloneBuild = outputType === OutputTypes.Standalone;
|
|
960
|
+
const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
|
|
961
|
+
const hasOutputs = (_a = cmpMeta.events) === null || _a === void 0 ? void 0 : _a.some((event) => !event.internal);
|
|
962
|
+
// Angular core imports for this component
|
|
963
|
+
const angularCoreImports = ['ChangeDetectionStrategy', 'ChangeDetectorRef', 'Component', 'ElementRef', 'NgZone'];
|
|
964
|
+
if (hasOutputs) {
|
|
965
|
+
angularCoreImports.push('EventEmitter', 'Output');
|
|
966
|
+
}
|
|
967
|
+
if (includeSingleComponentAngularModules) {
|
|
968
|
+
angularCoreImports.push('NgModule');
|
|
969
|
+
}
|
|
970
|
+
const imports = `/* tslint:disable */
|
|
971
|
+
/* auto-generated angular directive proxies */
|
|
972
|
+
${createImportStatement(angularCoreImports, '@angular/core')}
|
|
973
|
+
|
|
974
|
+
${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
|
|
975
|
+
// Type imports
|
|
976
|
+
let importLocation = componentCorePackage ? normalizePath(componentCorePackage) : normalizePath(componentsTypeFile);
|
|
977
|
+
importLocation += isCustomElementsBuild ? `/${customElementsDir}` : '';
|
|
978
|
+
const typeImports = `import ${isCustomElementsBuild ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
|
|
979
|
+
// defineCustomElement import
|
|
980
|
+
let sourceImport = '';
|
|
981
|
+
if (isCustomElementsBuild && componentCorePackage !== undefined) {
|
|
982
|
+
sourceImport = `import { defineCustomElement as define${tagNameAsPascal} } from '${normalizePath(componentCorePackage)}/${customElementsDir}/${cmpMeta.tagName}.js';\n`;
|
|
983
|
+
}
|
|
984
|
+
// Generate component definition
|
|
985
|
+
const filterInternalProps = (prop) => !prop.internal;
|
|
986
|
+
const mapInputProp = (prop) => {
|
|
987
|
+
var _a;
|
|
988
|
+
return ({
|
|
989
|
+
name: prop.name,
|
|
990
|
+
required: (_a = prop.required) !== null && _a !== void 0 ? _a : false,
|
|
991
|
+
});
|
|
992
|
+
};
|
|
993
|
+
const internalProps = [];
|
|
994
|
+
if (cmpMeta.properties) {
|
|
995
|
+
internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
|
|
996
|
+
}
|
|
997
|
+
const inputs = internalProps.map(mapInputProp);
|
|
998
|
+
if (cmpMeta.virtualProperties) {
|
|
999
|
+
inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
|
|
1000
|
+
}
|
|
1001
|
+
const orderedInputs = sortBy(inputs, (cip) => cip.name);
|
|
1002
|
+
const methods = [];
|
|
1003
|
+
if (cmpMeta.methods) {
|
|
1004
|
+
methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
|
|
1005
|
+
}
|
|
1006
|
+
const inlineComponentProps = outputTarget.inlineProperties ? internalProps : [];
|
|
1007
|
+
const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, orderedInputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps, cmpMeta.events || []);
|
|
1008
|
+
const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
|
|
1009
|
+
const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
|
|
1010
|
+
const proxyFileOutput = [componentDefinition, '\n'];
|
|
1011
|
+
if (includeSingleComponentAngularModules) {
|
|
1012
|
+
proxyFileOutput.push(moduleDefinition, '\n');
|
|
1013
|
+
}
|
|
1014
|
+
proxyFileOutput.push(componentTypeDefinition, '\n');
|
|
1015
|
+
const final = [imports, typeImports, sourceImport, ...proxyFileOutput];
|
|
1016
|
+
return final.join('\n') + '\n';
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Generate a barrel file that re-exports all components
|
|
1020
|
+
*/
|
|
1021
|
+
function generateBarrelFile(components, outputTarget) {
|
|
1022
|
+
const { outputType } = outputTarget;
|
|
1023
|
+
const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
|
|
1024
|
+
const header = `/* tslint:disable */
|
|
1025
|
+
/**
|
|
1026
|
+
* This file was automatically generated by the Stencil Angular Output Target.
|
|
1027
|
+
* Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
|
|
1028
|
+
*/\n\n`;
|
|
1029
|
+
const exports = components
|
|
1030
|
+
.map((component) => {
|
|
1031
|
+
const pascalName = dashToPascalCase(component.tagName);
|
|
1032
|
+
const moduleExport = includeSingleComponentAngularModules ? `, ${pascalName}Module` : '';
|
|
1033
|
+
return `export { ${pascalName}${moduleExport} } from './${component.tagName}';`;
|
|
1034
|
+
})
|
|
1035
|
+
.join('\n');
|
|
1036
|
+
return header + exports + '\n';
|
|
1037
|
+
}
|
|
928
1038
|
const GENERATED_DTS = 'components.d.ts';
|
|
929
1039
|
const IMPORT_TYPES = 'Components';
|
|
930
1040
|
|
package/dist/index.js
CHANGED
|
@@ -227,7 +227,7 @@ const createAngularComponentDefinition = (tagName, inputs, methods, includeImpor
|
|
|
227
227
|
const outputDeclarations = events
|
|
228
228
|
.filter((event) => !event.internal)
|
|
229
229
|
.map((event) => {
|
|
230
|
-
const camelCaseOutput = event.name.replace(/-([a-z])/g, (
|
|
230
|
+
const camelCaseOutput = event.name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
231
231
|
const outputType = `EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>`;
|
|
232
232
|
return `@Output() ${camelCaseOutput} = new ${outputType}();`;
|
|
233
233
|
});
|
|
@@ -275,7 +275,7 @@ const formatOutputType = (componentClassName, event) => {
|
|
|
275
275
|
*/
|
|
276
276
|
return Object.entries(event.complexType.references)
|
|
277
277
|
.filter(([_, refObject]) => refObject.location === 'local' || refObject.location === 'import')
|
|
278
|
-
.reduce((type, [src
|
|
278
|
+
.reduce((type, [src]) => {
|
|
279
279
|
let renamedType = type;
|
|
280
280
|
if (!type.startsWith(prefix)) {
|
|
281
281
|
if (type.startsWith('{') && type.endsWith('}')) {
|
|
@@ -295,28 +295,28 @@ const formatOutputType = (componentClassName, event) => {
|
|
|
295
295
|
renamedType = `I${componentClassName}${type}`;
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
|
+
const prefixedTypeName = `${prefix}${src}`;
|
|
298
299
|
return (renamedType
|
|
299
|
-
.replace(new RegExp(`^${src}$`, 'g'),
|
|
300
|
+
.replace(new RegExp(`^${src}$`, 'g'), prefixedTypeName)
|
|
300
301
|
// Capture all instances of the `src` field surrounded by non-word characters on each side and join them.
|
|
301
|
-
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return [p1, `I${componentClassName}${v.substring(1, v.length - 1)}`, p2].join('');
|
|
309
|
-
}
|
|
310
|
-
return [p1, dst, p2].join('');
|
|
302
|
+
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (_, p1, p2) => {
|
|
303
|
+
/**
|
|
304
|
+
* Replaces a complex type reference within a generic type.
|
|
305
|
+
* For example, remapping a type like `EventEmitter<CustomEvent<MyEvent<T>>>` to
|
|
306
|
+
* `EventEmitter<CustomEvent<IMyComponentMyEvent<IMyComponentT>>>`.
|
|
307
|
+
*/
|
|
308
|
+
return [p1, prefixedTypeName, p2].join('');
|
|
311
309
|
})
|
|
312
310
|
// Capture all instances that contain sub types, e.g. `IMyComponent.SomeMoreComplexType.SubType`.
|
|
313
311
|
.replace(new RegExp(`^${src}(\.\\w+)+$`, 'g'), (type) => {
|
|
314
|
-
return
|
|
312
|
+
return `${prefix}${src}.${type.split('.').slice(1).join('.')}`;
|
|
315
313
|
}));
|
|
316
314
|
}, event.complexType.original
|
|
315
|
+
.replace(/\/\/.*$/gm, '') // Remove single-line comments before collapsing newlines
|
|
317
316
|
.replace(/\n/g, ' ')
|
|
318
317
|
.replace(/\s{2,}/g, ' ')
|
|
319
|
-
.replace(/,\s*/g, ', ')
|
|
318
|
+
.replace(/,\s*/g, ', ')
|
|
319
|
+
.trim());
|
|
320
320
|
};
|
|
321
321
|
/**
|
|
322
322
|
* Creates a formatted comment block based on the JS doc comment.
|
|
@@ -763,13 +763,33 @@ async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components
|
|
|
763
763
|
const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
|
|
764
764
|
const rootDir = config.rootDir;
|
|
765
765
|
const pkgData = await readPackageJson(config, rootDir);
|
|
766
|
-
|
|
766
|
+
// esModules defaults to true, but only applies when outputType is 'scam' or 'standalone'
|
|
767
|
+
const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputTarget.outputType);
|
|
768
|
+
const useEsModules = isCustomElementsBuild && outputTarget.esModules === true;
|
|
767
769
|
const tasks = [
|
|
768
|
-
compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText),
|
|
769
770
|
copyResources(config, outputTarget),
|
|
770
|
-
generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget),
|
|
771
771
|
generateValueAccessors(compilerCtx, filteredComponents, outputTarget, config),
|
|
772
772
|
];
|
|
773
|
+
if (useEsModules) {
|
|
774
|
+
// Generate separate files for each component
|
|
775
|
+
const proxiesDir = path.dirname(outputTarget.directivesProxyFile);
|
|
776
|
+
for (const component of filteredComponents) {
|
|
777
|
+
const componentFile = path.join(proxiesDir, `${component.tagName}.ts`);
|
|
778
|
+
const componentText = generateComponentProxy(component, pkgData, outputTarget, rootDir);
|
|
779
|
+
tasks.push(compilerCtx.fs.writeFile(componentFile, componentText));
|
|
780
|
+
}
|
|
781
|
+
// Generate barrel file that re-exports all components
|
|
782
|
+
const barrelText = generateBarrelFile(filteredComponents, outputTarget);
|
|
783
|
+
tasks.push(compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, barrelText));
|
|
784
|
+
// Generate DIRECTIVES file (imports from barrel)
|
|
785
|
+
tasks.push(generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget));
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
788
|
+
// Generate single file with all components (original behavior)
|
|
789
|
+
const finalText = generateProxies(filteredComponents, pkgData, outputTarget, rootDir);
|
|
790
|
+
tasks.push(compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText));
|
|
791
|
+
tasks.push(generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget));
|
|
792
|
+
}
|
|
773
793
|
// Generate transformer script if transformTag is enabled
|
|
774
794
|
if (outputTarget.transformTag) {
|
|
775
795
|
// Read the Angular library's package.json to get its name
|
|
@@ -917,6 +937,96 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
|
|
|
917
937
|
const final = [imports, typeImports, sourceImports, ...proxyFileOutput];
|
|
918
938
|
return final.join('\n') + '\n';
|
|
919
939
|
}
|
|
940
|
+
/**
|
|
941
|
+
* Generate a single component proxy file for ES modules output
|
|
942
|
+
*/
|
|
943
|
+
function generateComponentProxy(cmpMeta, pkgData, outputTarget, rootDir) {
|
|
944
|
+
var _a;
|
|
945
|
+
const { outputType, componentCorePackage, customElementsDir } = outputTarget;
|
|
946
|
+
const distTypesDir = path.dirname(pkgData.types);
|
|
947
|
+
const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
|
|
948
|
+
const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
|
|
949
|
+
const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
|
|
950
|
+
const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputType);
|
|
951
|
+
const isStandaloneBuild = outputType === OutputTypes.Standalone;
|
|
952
|
+
const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
|
|
953
|
+
const hasOutputs = (_a = cmpMeta.events) === null || _a === void 0 ? void 0 : _a.some((event) => !event.internal);
|
|
954
|
+
// Angular core imports for this component
|
|
955
|
+
const angularCoreImports = ['ChangeDetectionStrategy', 'ChangeDetectorRef', 'Component', 'ElementRef', 'NgZone'];
|
|
956
|
+
if (hasOutputs) {
|
|
957
|
+
angularCoreImports.push('EventEmitter', 'Output');
|
|
958
|
+
}
|
|
959
|
+
if (includeSingleComponentAngularModules) {
|
|
960
|
+
angularCoreImports.push('NgModule');
|
|
961
|
+
}
|
|
962
|
+
const imports = `/* tslint:disable */
|
|
963
|
+
/* auto-generated angular directive proxies */
|
|
964
|
+
${createImportStatement(angularCoreImports, '@angular/core')}
|
|
965
|
+
|
|
966
|
+
${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
|
|
967
|
+
// Type imports
|
|
968
|
+
let importLocation = componentCorePackage ? normalizePath(componentCorePackage) : normalizePath(componentsTypeFile);
|
|
969
|
+
importLocation += isCustomElementsBuild ? `/${customElementsDir}` : '';
|
|
970
|
+
const typeImports = `import ${isCustomElementsBuild ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
|
|
971
|
+
// defineCustomElement import
|
|
972
|
+
let sourceImport = '';
|
|
973
|
+
if (isCustomElementsBuild && componentCorePackage !== undefined) {
|
|
974
|
+
sourceImport = `import { defineCustomElement as define${tagNameAsPascal} } from '${normalizePath(componentCorePackage)}/${customElementsDir}/${cmpMeta.tagName}.js';\n`;
|
|
975
|
+
}
|
|
976
|
+
// Generate component definition
|
|
977
|
+
const filterInternalProps = (prop) => !prop.internal;
|
|
978
|
+
const mapInputProp = (prop) => {
|
|
979
|
+
var _a;
|
|
980
|
+
return ({
|
|
981
|
+
name: prop.name,
|
|
982
|
+
required: (_a = prop.required) !== null && _a !== void 0 ? _a : false,
|
|
983
|
+
});
|
|
984
|
+
};
|
|
985
|
+
const internalProps = [];
|
|
986
|
+
if (cmpMeta.properties) {
|
|
987
|
+
internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
|
|
988
|
+
}
|
|
989
|
+
const inputs = internalProps.map(mapInputProp);
|
|
990
|
+
if (cmpMeta.virtualProperties) {
|
|
991
|
+
inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
|
|
992
|
+
}
|
|
993
|
+
const orderedInputs = sortBy(inputs, (cip) => cip.name);
|
|
994
|
+
const methods = [];
|
|
995
|
+
if (cmpMeta.methods) {
|
|
996
|
+
methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
|
|
997
|
+
}
|
|
998
|
+
const inlineComponentProps = outputTarget.inlineProperties ? internalProps : [];
|
|
999
|
+
const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, orderedInputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps, cmpMeta.events || []);
|
|
1000
|
+
const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
|
|
1001
|
+
const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
|
|
1002
|
+
const proxyFileOutput = [componentDefinition, '\n'];
|
|
1003
|
+
if (includeSingleComponentAngularModules) {
|
|
1004
|
+
proxyFileOutput.push(moduleDefinition, '\n');
|
|
1005
|
+
}
|
|
1006
|
+
proxyFileOutput.push(componentTypeDefinition, '\n');
|
|
1007
|
+
const final = [imports, typeImports, sourceImport, ...proxyFileOutput];
|
|
1008
|
+
return final.join('\n') + '\n';
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Generate a barrel file that re-exports all components
|
|
1012
|
+
*/
|
|
1013
|
+
function generateBarrelFile(components, outputTarget) {
|
|
1014
|
+
const { outputType } = outputTarget;
|
|
1015
|
+
const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
|
|
1016
|
+
const header = `/* tslint:disable */
|
|
1017
|
+
/**
|
|
1018
|
+
* This file was automatically generated by the Stencil Angular Output Target.
|
|
1019
|
+
* Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
|
|
1020
|
+
*/\n\n`;
|
|
1021
|
+
const exports = components
|
|
1022
|
+
.map((component) => {
|
|
1023
|
+
const pascalName = dashToPascalCase(component.tagName);
|
|
1024
|
+
const moduleExport = includeSingleComponentAngularModules ? `, ${pascalName}Module` : '';
|
|
1025
|
+
return `export { ${pascalName}${moduleExport} } from './${component.tagName}';`;
|
|
1026
|
+
})
|
|
1027
|
+
.join('\n');
|
|
1028
|
+
return header + exports + '\n';
|
|
1029
|
+
}
|
|
920
1030
|
const GENERATED_DTS = 'components.d.ts';
|
|
921
1031
|
const IMPORT_TYPES = 'Components';
|
|
922
1032
|
|
package/dist/output-angular.d.ts
CHANGED
|
@@ -2,3 +2,11 @@ import type { CompilerCtx, ComponentCompilerMeta, Config } from '@stencil/core/i
|
|
|
2
2
|
import type { OutputTargetAngular, PackageJSON } from './types';
|
|
3
3
|
export declare function angularDirectiveProxyOutput(compilerCtx: CompilerCtx, outputTarget: OutputTargetAngular, components: ComponentCompilerMeta[], config: Config): Promise<void>;
|
|
4
4
|
export declare function generateProxies(components: ComponentCompilerMeta[], pkgData: PackageJSON, outputTarget: OutputTargetAngular, rootDir: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Generate a single component proxy file for ES modules output
|
|
7
|
+
*/
|
|
8
|
+
export declare function generateComponentProxy(cmpMeta: ComponentCompilerMeta, pkgData: PackageJSON, outputTarget: OutputTargetAngular, rootDir: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Generate a barrel file that re-exports all components
|
|
11
|
+
*/
|
|
12
|
+
export declare function generateBarrelFile(components: ComponentCompilerMeta[], outputTarget: OutputTargetAngular): string;
|
package/dist/output-angular.js
CHANGED
|
@@ -9,13 +9,33 @@ export async function angularDirectiveProxyOutput(compilerCtx, outputTarget, com
|
|
|
9
9
|
const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
|
|
10
10
|
const rootDir = config.rootDir;
|
|
11
11
|
const pkgData = await readPackageJson(config, rootDir);
|
|
12
|
-
|
|
12
|
+
// esModules defaults to true, but only applies when outputType is 'scam' or 'standalone'
|
|
13
|
+
const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputTarget.outputType);
|
|
14
|
+
const useEsModules = isCustomElementsBuild && outputTarget.esModules === true;
|
|
13
15
|
const tasks = [
|
|
14
|
-
compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText),
|
|
15
16
|
copyResources(config, outputTarget),
|
|
16
|
-
generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget),
|
|
17
17
|
generateValueAccessors(compilerCtx, filteredComponents, outputTarget, config),
|
|
18
18
|
];
|
|
19
|
+
if (useEsModules) {
|
|
20
|
+
// Generate separate files for each component
|
|
21
|
+
const proxiesDir = path.dirname(outputTarget.directivesProxyFile);
|
|
22
|
+
for (const component of filteredComponents) {
|
|
23
|
+
const componentFile = path.join(proxiesDir, `${component.tagName}.ts`);
|
|
24
|
+
const componentText = generateComponentProxy(component, pkgData, outputTarget, rootDir);
|
|
25
|
+
tasks.push(compilerCtx.fs.writeFile(componentFile, componentText));
|
|
26
|
+
}
|
|
27
|
+
// Generate barrel file that re-exports all components
|
|
28
|
+
const barrelText = generateBarrelFile(filteredComponents, outputTarget);
|
|
29
|
+
tasks.push(compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, barrelText));
|
|
30
|
+
// Generate DIRECTIVES file (imports from barrel)
|
|
31
|
+
tasks.push(generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// Generate single file with all components (original behavior)
|
|
35
|
+
const finalText = generateProxies(filteredComponents, pkgData, outputTarget, rootDir);
|
|
36
|
+
tasks.push(compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText));
|
|
37
|
+
tasks.push(generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget));
|
|
38
|
+
}
|
|
19
39
|
// Generate transformer script if transformTag is enabled
|
|
20
40
|
if (outputTarget.transformTag) {
|
|
21
41
|
// Read the Angular library's package.json to get its name
|
|
@@ -163,5 +183,95 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
|
|
|
163
183
|
const final = [imports, typeImports, sourceImports, ...proxyFileOutput];
|
|
164
184
|
return final.join('\n') + '\n';
|
|
165
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Generate a single component proxy file for ES modules output
|
|
188
|
+
*/
|
|
189
|
+
export function generateComponentProxy(cmpMeta, pkgData, outputTarget, rootDir) {
|
|
190
|
+
var _a;
|
|
191
|
+
const { outputType, componentCorePackage, customElementsDir } = outputTarget;
|
|
192
|
+
const distTypesDir = path.dirname(pkgData.types);
|
|
193
|
+
const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
|
|
194
|
+
const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
|
|
195
|
+
const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
|
|
196
|
+
const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputType);
|
|
197
|
+
const isStandaloneBuild = outputType === OutputTypes.Standalone;
|
|
198
|
+
const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
|
|
199
|
+
const hasOutputs = (_a = cmpMeta.events) === null || _a === void 0 ? void 0 : _a.some((event) => !event.internal);
|
|
200
|
+
// Angular core imports for this component
|
|
201
|
+
const angularCoreImports = ['ChangeDetectionStrategy', 'ChangeDetectorRef', 'Component', 'ElementRef', 'NgZone'];
|
|
202
|
+
if (hasOutputs) {
|
|
203
|
+
angularCoreImports.push('EventEmitter', 'Output');
|
|
204
|
+
}
|
|
205
|
+
if (includeSingleComponentAngularModules) {
|
|
206
|
+
angularCoreImports.push('NgModule');
|
|
207
|
+
}
|
|
208
|
+
const imports = `/* tslint:disable */
|
|
209
|
+
/* auto-generated angular directive proxies */
|
|
210
|
+
${createImportStatement(angularCoreImports, '@angular/core')}
|
|
211
|
+
|
|
212
|
+
${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
|
|
213
|
+
// Type imports
|
|
214
|
+
let importLocation = componentCorePackage ? normalizePath(componentCorePackage) : normalizePath(componentsTypeFile);
|
|
215
|
+
importLocation += isCustomElementsBuild ? `/${customElementsDir}` : '';
|
|
216
|
+
const typeImports = `import ${isCustomElementsBuild ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
|
|
217
|
+
// defineCustomElement import
|
|
218
|
+
let sourceImport = '';
|
|
219
|
+
if (isCustomElementsBuild && componentCorePackage !== undefined) {
|
|
220
|
+
sourceImport = `import { defineCustomElement as define${tagNameAsPascal} } from '${normalizePath(componentCorePackage)}/${customElementsDir}/${cmpMeta.tagName}.js';\n`;
|
|
221
|
+
}
|
|
222
|
+
// Generate component definition
|
|
223
|
+
const filterInternalProps = (prop) => !prop.internal;
|
|
224
|
+
const mapInputProp = (prop) => {
|
|
225
|
+
var _a;
|
|
226
|
+
return ({
|
|
227
|
+
name: prop.name,
|
|
228
|
+
required: (_a = prop.required) !== null && _a !== void 0 ? _a : false,
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
const internalProps = [];
|
|
232
|
+
if (cmpMeta.properties) {
|
|
233
|
+
internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
|
|
234
|
+
}
|
|
235
|
+
const inputs = internalProps.map(mapInputProp);
|
|
236
|
+
if (cmpMeta.virtualProperties) {
|
|
237
|
+
inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
|
|
238
|
+
}
|
|
239
|
+
const orderedInputs = sortBy(inputs, (cip) => cip.name);
|
|
240
|
+
const methods = [];
|
|
241
|
+
if (cmpMeta.methods) {
|
|
242
|
+
methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
|
|
243
|
+
}
|
|
244
|
+
const inlineComponentProps = outputTarget.inlineProperties ? internalProps : [];
|
|
245
|
+
const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, orderedInputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps, cmpMeta.events || []);
|
|
246
|
+
const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
|
|
247
|
+
const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
|
|
248
|
+
const proxyFileOutput = [componentDefinition, '\n'];
|
|
249
|
+
if (includeSingleComponentAngularModules) {
|
|
250
|
+
proxyFileOutput.push(moduleDefinition, '\n');
|
|
251
|
+
}
|
|
252
|
+
proxyFileOutput.push(componentTypeDefinition, '\n');
|
|
253
|
+
const final = [imports, typeImports, sourceImport, ...proxyFileOutput];
|
|
254
|
+
return final.join('\n') + '\n';
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Generate a barrel file that re-exports all components
|
|
258
|
+
*/
|
|
259
|
+
export function generateBarrelFile(components, outputTarget) {
|
|
260
|
+
const { outputType } = outputTarget;
|
|
261
|
+
const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
|
|
262
|
+
const header = `/* tslint:disable */
|
|
263
|
+
/**
|
|
264
|
+
* This file was automatically generated by the Stencil Angular Output Target.
|
|
265
|
+
* Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
|
|
266
|
+
*/\n\n`;
|
|
267
|
+
const exports = components
|
|
268
|
+
.map((component) => {
|
|
269
|
+
const pascalName = dashToPascalCase(component.tagName);
|
|
270
|
+
const moduleExport = includeSingleComponentAngularModules ? `, ${pascalName}Module` : '';
|
|
271
|
+
return `export { ${pascalName}${moduleExport} } from './${component.tagName}';`;
|
|
272
|
+
})
|
|
273
|
+
.join('\n');
|
|
274
|
+
return header + exports + '\n';
|
|
275
|
+
}
|
|
166
276
|
const GENERATED_DTS = 'components.d.ts';
|
|
167
277
|
const IMPORT_TYPES = 'Components';
|
package/dist/types.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The type of output that can be generated with the Angular output target.
|
|
3
|
-
* - `component` - Generate many component wrappers tied to a single Angular module (lazy/hydrated approach).
|
|
4
|
-
* - `scam` - Generate a Single Component Angular Module for each component.
|
|
5
|
-
* - `standalone` - Generates standalone components.
|
|
3
|
+
* - `component` - Generate many component wrappers tied to a single Angular module (requires `dist`, lazy/hydrated approach).
|
|
4
|
+
* - `scam` - Generate a Single Component Angular Module for each component (requires `dist-custom-elements` output).
|
|
5
|
+
* - `standalone` - Generates standalone components (requires `dist-custom-elements` output).
|
|
6
6
|
*/
|
|
7
7
|
export type OutputType = 'component' | 'scam' | 'standalone';
|
|
8
8
|
export interface OutputTargetAngular {
|
|
@@ -16,15 +16,33 @@ export interface OutputTargetAngular {
|
|
|
16
16
|
* or a relative path from the root directory of the Stencil library.
|
|
17
17
|
*/
|
|
18
18
|
directivesProxyFile: string;
|
|
19
|
+
/**
|
|
20
|
+
* Optional path to generate a file containing a `DIRECTIVES` array constant.
|
|
21
|
+
* This is primarily useful with `outputType: 'component'` (lazy-loaded) where you want
|
|
22
|
+
* to declare all components in a shared NgModule:
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { DIRECTIVES } from './directives';
|
|
26
|
+
*
|
|
27
|
+
* @NgModule({
|
|
28
|
+
* declarations: [...DIRECTIVES],
|
|
29
|
+
* exports: [...DIRECTIVES],
|
|
30
|
+
* })
|
|
31
|
+
* export class ComponentLibraryModule {}
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* This option is less relevant for `outputType: 'scam'` or `'standalone'` where
|
|
35
|
+
* consumers typically import individual component modules or standalone components directly.
|
|
36
|
+
*/
|
|
19
37
|
directivesArrayFile?: string;
|
|
20
38
|
valueAccessorConfigs?: ValueAccessorConfig[];
|
|
21
39
|
excludeComponents?: string[];
|
|
22
40
|
customElementsDir?: string;
|
|
23
41
|
/**
|
|
24
42
|
* The type of output that should be generated.
|
|
25
|
-
* - `component` - Generate many component wrappers tied to a single Angular module (lazy/hydrated approach).
|
|
26
|
-
* - `scam` - Generate a Single Component Angular Module for each component.
|
|
27
|
-
* - `standalone` - (default) Generates standalone components.
|
|
43
|
+
* - `component` - Generate many component wrappers tied to a single Angular module (requires `dist`, lazy/hydrated approach).
|
|
44
|
+
* - `scam` - Generate a Single Component Angular Module for each component (requires `dist-custom-elements` output).
|
|
45
|
+
* - `standalone` - (default) Generates standalone components (requires `dist-custom-elements` output).
|
|
28
46
|
*/
|
|
29
47
|
outputType?: OutputType;
|
|
30
48
|
/**
|
|
@@ -69,6 +87,13 @@ export interface OutputTargetAngular {
|
|
|
69
87
|
* @default false
|
|
70
88
|
*/
|
|
71
89
|
transformTag?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* If `true`, the output target will generate a separate ES module for each Angular component wrapper.
|
|
92
|
+
* This enables better tree-shaking as bundlers can exclude unused components.
|
|
93
|
+
* This option only applies when `outputType` is `'scam'` or `'standalone'` (i.e., using `dist-custom-elements`).
|
|
94
|
+
* @default false
|
|
95
|
+
*/
|
|
96
|
+
esModules?: boolean;
|
|
72
97
|
}
|
|
73
98
|
export type ValueAccessorTypes = 'text' | 'radio' | 'select' | 'number' | 'boolean';
|
|
74
99
|
export interface ValueAccessorConfig {
|