@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.
@@ -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, (match, letter) => letter.toUpperCase());
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, dst]) => {
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'), `${dst}`)
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'), (v, p1, p2) => {
164
- if ((dst === null || dst === void 0 ? void 0 : dst.location) === 'import') {
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, `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 `I${componentClassName}${src}.${type.split('.').slice(1).join('.')}`;
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, (match, letter) => letter.toUpperCase());
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, dst]) => {
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'), `${dst}`)
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'), (v, p1, p2) => {
310
- if ((dst === null || dst === void 0 ? void 0 : dst.location) === 'import') {
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, `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 `I${componentClassName}${src}.${type.split('.').slice(1).join('.')}`;
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
- const finalText = generateProxies(filteredComponents, pkgData, outputTarget, config.rootDir);
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, (match, letter) => letter.toUpperCase());
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, dst]) => {
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'), `${dst}`)
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'), (v, p1, p2) => {
302
- if ((dst === null || dst === void 0 ? void 0 : dst.location) === 'import') {
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, `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 `I${componentClassName}${src}.${type.split('.').slice(1).join('.')}`;
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
- const finalText = generateProxies(filteredComponents, pkgData, outputTarget, config.rootDir);
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
 
@@ -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;
@@ -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
- const finalText = generateProxies(filteredComponents, pkgData, outputTarget, config.rootDir);
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stencil/angular-output-target",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Angular output target for @stencil/core components.",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.js",