@stencil/angular-output-target 1.4.1 → 1.5.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/index.js CHANGED
@@ -118,7 +118,9 @@ const createComponentEventTypeImports = (componentTagName, events, options) => {
118
118
  const imports = [];
119
119
  const namedImports = new Set();
120
120
  const isCustomElementsBuild = isOutputTypeCustomElementsBuild(options.outputType);
121
- const importPathName = normalizePath(componentCorePackage) + (isCustomElementsBuild ? `/${customElementsDir}` : '');
121
+ const exportMapEntry = isCustomElementsBuild ? '/standalone' : '/loader';
122
+ const importPathName = normalizePath(componentCorePackage) +
123
+ (options.exportMaps ? exportMapEntry : isCustomElementsBuild ? `/${customElementsDir}` : '');
122
124
  /**
123
125
  * Each component's events are typed with the per-component CustomEvent type
124
126
  * generated by Stencil (e.g. `MyComponentCustomEvent<Detail>`), which carries a
@@ -171,21 +173,33 @@ function createPropertyDeclaration(prop, type, inlinePropertyAsSetter = false) {
171
173
  }
172
174
  }
173
175
  /**
174
- * Creates a formatted inputs text with required declaration.
176
+ * The transform function to reference in generated code, exported from
177
+ * `angular-component-lib/boolean-attribute`.
178
+ */
179
+ const INPUT_TRANSFORM_FUNCTION = 'nullableBooleanAttribute';
180
+ /**
181
+ * Creates a formatted inputs text with required and transform declarations.
182
+ *
183
+ * Inputs are emitted as bare strings unless they carry options, in which case Angular's object
184
+ * form is used (e.g. `{ name: 'myInput', required: true }`).
175
185
  *
176
- * @param prop A ComponentCompilerEvent or ComponentCompilerProperty to turn into a property declaration.
177
186
  * @param inputs The inputs of the Stencil component (e.g. [{name: 'myInput', required: true]).
178
187
  * @returns The inputs list declaration as a string.
179
188
  */
180
189
  function formatInputs(inputs) {
181
190
  return inputs
182
191
  .map((item) => {
192
+ const options = [];
183
193
  if (item.required) {
184
- return `{ name: '${item.name}', required: true }`;
194
+ options.push('required: true');
195
+ }
196
+ if (item.transform) {
197
+ options.push(`transform: ${INPUT_TRANSFORM_FUNCTION}`);
185
198
  }
186
- else {
199
+ if (options.length === 0) {
187
200
  return `'${item.name}'`;
188
201
  }
202
+ return `{ name: '${item.name}', ${options.join(', ')} }`;
189
203
  })
190
204
  .join(', ');
191
205
  }
@@ -346,14 +360,16 @@ const createDocComment = (doc) => {
346
360
  * @param events The events to generate the interface properties for.
347
361
  * @param componentCorePackage The component core package.
348
362
  * @param customElementsDir The custom elements directory.
363
+ * @param exportMaps Import through the package's `exports` map instead of a deep path.
349
364
  * @returns The component interface type definition as a string.
350
365
  */
351
- const createComponentTypeDefinition = (outputType, tagNameAsPascal, events, componentCorePackage, customElementsDir) => {
366
+ const createComponentTypeDefinition = (outputType, tagNameAsPascal, events, componentCorePackage, customElementsDir, exportMaps = false) => {
352
367
  const publicEvents = events.filter((ev) => !ev.internal);
353
368
  const eventTypeImports = createComponentEventTypeImports(tagNameAsPascal, publicEvents, {
354
369
  componentCorePackage,
355
370
  customElementsDir,
356
371
  outputType,
372
+ exportMaps,
357
373
  });
358
374
  const eventTypes = publicEvents.map((event) => createPropertyDeclaration(event, `EventEmitter<${tagNameAsPascal}CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>`));
359
375
  const interfaceDeclaration = `export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {`;
@@ -471,10 +487,14 @@ export class ${moduleClassName} { }`;
471
487
  * This script patches component selectors in the built Angular library to use the
472
488
  * transformed tag names (e.g., 'my-component' -> 'v1-my-component').
473
489
  */
474
- async function generateTransformTagScript(compilerCtx, components, outputTarget, packageName) {
490
+ async function generateTransformTagScript(compilerCtx, components, outputTarget,
491
+ /** Import through the package's `exports` map (`<pkg>/standalone`) instead of a deep path. */
492
+ exportMaps = false) {
475
493
  const scriptsDirectory = path.join(path.dirname(outputTarget.directivesProxyFile), '../../scripts');
476
494
  const customElementsDir = outputTarget.customElementsDir || 'dist/components';
477
- const stencilImportPath = `${outputTarget.componentCorePackage}/${customElementsDir}/index.js`;
495
+ const stencilImportPath = exportMaps
496
+ ? `${outputTarget.componentCorePackage}/standalone`
497
+ : `${outputTarget.componentCorePackage}/${customElementsDir}/index.js`;
478
498
  // Generate the mappings object
479
499
  const mappings = components
480
500
  .map((component) => {
@@ -774,6 +794,73 @@ stencilSetTagTransformer(\${transformerArg});
774
794
 
775
795
  const __filename = fileURLToPath(import.meta.url);
776
796
  const __dirname = path.dirname(__filename);
797
+ const filterInternalProps = (prop) => !prop.internal;
798
+ /**
799
+ * Whether a property declares an input transform.
800
+ *
801
+ * This is the single definition of the rule. Both the generated declaration and the import that
802
+ * satisfies it are derived from it, so widening the rule can't leave a generated file referencing
803
+ * a transform it never imports.
804
+ *
805
+ * @param prop The property compiler metadata.
806
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
807
+ * @returns `true` when the property should be transformed.
808
+ */
809
+ const isTransformedProp = (prop, booleanAttributes) => booleanAttributes && prop.type === 'boolean';
810
+ /**
811
+ * Maps a Stencil property to an Angular input declaration.
812
+ *
813
+ * Virtual properties are mapped with `booleanAttributes` left at its default of `false`. They
814
+ * carry a free-form type string and no `required` flag, so they are never transformed and always
815
+ * come out as optional.
816
+ *
817
+ * @param prop The property compiler metadata.
818
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
819
+ * @returns The Angular input declaration.
820
+ */
821
+ const mapInputProp = (prop, booleanAttributes = false) => ({
822
+ name: prop.name,
823
+ required: prop.required ?? false,
824
+ transform: isTransformedProp(prop, booleanAttributes) ? true : undefined,
825
+ });
826
+ /**
827
+ * Whether any of a component's properties will declare an input transform, which determines
828
+ * whether the transform needs to be imported.
829
+ *
830
+ * @param components The components in the generated file.
831
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
832
+ * @returns `true` when at least one property is transformed.
833
+ */
834
+ const usesInputTransform = (components, booleanAttributes) => components.some((cmpMeta) => (cmpMeta.properties ?? []).filter(filterInternalProps).some((prop) => isTransformedProp(prop, booleanAttributes)));
835
+ /**
836
+ * Whether the Stencil project generates a package.json `exports` map. Deep `dist/` imports are
837
+ * blocked for consumers then, so the proxies import through the map's entries instead
838
+ * (`<pkg>/<tag>`, `<pkg>/loader`, `<pkg>/standalone`).
839
+ */
840
+ function usesExportMaps(config) {
841
+ // Stencil v5 only: v4's map has no `./standalone` entry, and its root types aren't
842
+ // guaranteed to export the component types.
843
+ const isV5 = (config.outputTargets || []).some((o) => ['loader-bundle', 'standalone', 'ssr', 'types'].includes(o.type));
844
+ return isV5 && config.generateExportMaps === true;
845
+ }
846
+ /**
847
+ * The module specifier a proxy imports a component's `defineCustomElement` from.
848
+ */
849
+ function getComponentModule(config, outputTarget, tagName) {
850
+ const basePkg = normalizePath(outputTarget.componentCorePackage);
851
+ if (usesExportMaps(config)) {
852
+ return `${basePkg}/${tagName}`;
853
+ }
854
+ return `${basePkg}/${outputTarget.customElementsDir}/${tagName}.js`;
855
+ }
856
+ /**
857
+ * The relative import of the project's own `components.d.ts`, for when there's no
858
+ * `componentCorePackage`. Only resolved when needed: package.json may have no `types` field.
859
+ */
860
+ function getComponentsTypeFile(pkgData, outputTarget, rootDir) {
861
+ const dtsFilePath = path.join(rootDir, path.dirname(pkgData.types), GENERATED_DTS);
862
+ return relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
863
+ }
777
864
  async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
778
865
  const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
779
866
  const rootDir = config.rootDir;
@@ -807,27 +894,7 @@ async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components
807
894
  }
808
895
  // Generate transformer script if transformTag is enabled
809
896
  if (outputTarget.transformTag) {
810
- // Read the Angular library's package.json to get its name
811
- // directivesProxyFile is like: projects/library/src/directives/proxies.ts
812
- // We need to go up to: projects/library/package.json
813
- const angularLibraryDir = path.dirname(path.dirname(path.dirname(outputTarget.directivesProxyFile)));
814
- const angularPkgJsonPath = path.join(angularLibraryDir, 'package.json');
815
- let angularPackageName = '';
816
- try {
817
- const angularPkgJson = JSON.parse(await compilerCtx.fs.readFile(angularPkgJsonPath));
818
- if (angularPkgJson.name) {
819
- angularPackageName = angularPkgJson.name;
820
- }
821
- }
822
- catch (e) {
823
- throw new Error(`Could not read Angular library package.json at ${angularPkgJsonPath}. ` +
824
- `The package name is required to generate the transformTag patch script.`);
825
- }
826
- if (!angularPackageName) {
827
- throw new Error(`Angular library package.json at ${angularPkgJsonPath} does not have a "name" field. ` +
828
- `The package name is required to generate the transformTag patch script.`);
829
- }
830
- tasks.push(generateTransformTagScript(compilerCtx, filteredComponents, outputTarget));
897
+ tasks.push(generateTransformTagScript(compilerCtx, filteredComponents, outputTarget, usesExportMaps(config)));
831
898
  }
832
899
  await Promise.all(tasks);
833
900
  }
@@ -851,10 +918,7 @@ async function copyResources(config, outputTarget) {
851
918
  ], srcDirectory);
852
919
  }
853
920
  function generateProxies(components, pkgData, outputTarget, rootDir, config) {
854
- const distTypesDir = path.dirname(pkgData.types);
855
- const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
856
921
  const { outputType } = outputTarget;
857
- const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
858
922
  const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
859
923
  const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputType);
860
924
  const isStandaloneBuild = outputType === OutputTypes.Standalone;
@@ -871,6 +935,14 @@ function generateProxies(components, pkgData, outputTarget, rootDir, config) {
871
935
  * The collection of named imports from the angular-component-lib/utils.
872
936
  */
873
937
  const componentLibImports = ['ProxyCmp'];
938
+ /**
939
+ * The input transform lives in its own module so that it carries no runtime imports.
940
+ */
941
+ let transformImport = '';
942
+ const booleanAttributes = outputTarget.booleanAttributes === true;
943
+ if (usesInputTransform(components, booleanAttributes)) {
944
+ transformImport = `\n${createImportStatement([INPUT_TRANSFORM_FUNCTION], './angular-component-lib/boolean-attribute')}`;
945
+ }
874
946
  if (includeSingleComponentAngularModules) {
875
947
  angularCoreImports.push('NgModule');
876
948
  }
@@ -878,7 +950,7 @@ function generateProxies(components, pkgData, outputTarget, rootDir, config) {
878
950
  /* auto-generated angular directive proxies */
879
951
  ${createImportStatement(angularCoreImports, '@angular/core')}
880
952
 
881
- ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`;
953
+ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}${transformImport}\n`;
882
954
  /**
883
955
  * Generate JSX import type from correct location.
884
956
  * When using custom elements build, we need to import from
@@ -888,7 +960,7 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
888
960
  const generateTypeImports = () => {
889
961
  const importLocation = outputTarget.componentCorePackage
890
962
  ? getPathToComponentTypes(config, outputTarget)
891
- : normalizePath(componentsTypeFile);
963
+ : normalizePath(getComponentsTypeFile(pkgData, outputTarget, rootDir));
892
964
  return `import ${isCustomElementsBuild ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
893
965
  };
894
966
  const typeImports = generateTypeImports();
@@ -902,17 +974,11 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
902
974
  if (isCustomElementsBuild && outputTarget.componentCorePackage !== undefined) {
903
975
  const cmpImports = components.map((component) => {
904
976
  const pascalImport = dashToPascalCase(component.tagName);
905
- return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir}/${component.tagName}.js';`;
977
+ return `import { defineCustomElement as define${pascalImport} } from '${getComponentModule(config, outputTarget, component.tagName)}';`;
906
978
  });
907
979
  sourceImports = cmpImports.join('\n');
908
980
  }
909
981
  const proxyFileOutput = [];
910
- const filterInternalProps = (prop) => !prop.internal;
911
- // Ensure that virtual properties has required as false.
912
- const mapInputProp = (prop) => ({
913
- name: prop.name,
914
- required: prop.required ?? false,
915
- });
916
982
  const { componentCorePackage, customElementsDir } = outputTarget;
917
983
  for (let cmpMeta of components) {
918
984
  const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
@@ -920,9 +986,9 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
920
986
  if (cmpMeta.properties) {
921
987
  internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
922
988
  }
923
- const inputs = internalProps.map(mapInputProp);
989
+ const inputs = internalProps.map((prop) => mapInputProp(prop, booleanAttributes));
924
990
  if (cmpMeta.virtualProperties) {
925
- inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
991
+ inputs.push(...cmpMeta.virtualProperties.map((prop) => mapInputProp(prop)));
926
992
  }
927
993
  const orderedInputs = sortBy(inputs, (cip) => cip.name);
928
994
  const methods = [];
@@ -938,7 +1004,7 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
938
1004
  */
939
1005
  const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, orderedInputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps, cmpMeta.events || []);
940
1006
  const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
941
- const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
1007
+ const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir, usesExportMaps(config));
942
1008
  proxyFileOutput.push(componentDefinition, '\n');
943
1009
  if (includeSingleComponentAngularModules) {
944
1010
  proxyFileOutput.push(moduleDefinition, '\n');
@@ -953,9 +1019,6 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
953
1019
  */
954
1020
  function generateComponentProxy(cmpMeta, pkgData, outputTarget, rootDir, config) {
955
1021
  const { outputType, componentCorePackage, customElementsDir } = outputTarget;
956
- const distTypesDir = path.dirname(pkgData.types);
957
- const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
958
- const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
959
1022
  const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
960
1023
  const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputType);
961
1024
  const isStandaloneBuild = outputType === OutputTypes.Standalone;
@@ -969,34 +1032,36 @@ function generateComponentProxy(cmpMeta, pkgData, outputTarget, rootDir, config)
969
1032
  if (includeSingleComponentAngularModules) {
970
1033
  angularCoreImports.push('NgModule');
971
1034
  }
1035
+ const booleanAttributes = outputTarget.booleanAttributes === true;
1036
+ const componentLibImports = ['ProxyCmp'];
1037
+ // The input transform lives in its own module so that it carries no runtime imports.
1038
+ let transformImport = '';
1039
+ if (usesInputTransform([cmpMeta], booleanAttributes)) {
1040
+ transformImport = `\n${createImportStatement([INPUT_TRANSFORM_FUNCTION], './angular-component-lib/boolean-attribute')}`;
1041
+ }
972
1042
  const imports = `/* tslint:disable */
973
1043
  /* auto-generated angular directive proxies */
974
1044
  ${createImportStatement(angularCoreImports, '@angular/core')}
975
1045
 
976
- ${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
1046
+ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}${transformImport}\n`;
977
1047
  // Type imports
978
1048
  const importLocation = componentCorePackage
979
1049
  ? getPathToComponentTypes(config, outputTarget)
980
- : normalizePath(componentsTypeFile);
1050
+ : normalizePath(getComponentsTypeFile(pkgData, outputTarget, rootDir));
981
1051
  const typeImports = `import ${isCustomElementsBuild ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
982
1052
  // defineCustomElement import
983
1053
  let sourceImport = '';
984
1054
  if (isCustomElementsBuild && componentCorePackage !== undefined) {
985
- sourceImport = `import { defineCustomElement as define${tagNameAsPascal} } from '${normalizePath(componentCorePackage)}/${customElementsDir}/${cmpMeta.tagName}.js';\n`;
1055
+ sourceImport = `import { defineCustomElement as define${tagNameAsPascal} } from '${getComponentModule(config, outputTarget, cmpMeta.tagName)}';\n`;
986
1056
  }
987
1057
  // Generate component definition
988
- const filterInternalProps = (prop) => !prop.internal;
989
- const mapInputProp = (prop) => ({
990
- name: prop.name,
991
- required: prop.required ?? false,
992
- });
993
1058
  const internalProps = [];
994
1059
  if (cmpMeta.properties) {
995
1060
  internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
996
1061
  }
997
- const inputs = internalProps.map(mapInputProp);
1062
+ const inputs = internalProps.map((prop) => mapInputProp(prop, booleanAttributes));
998
1063
  if (cmpMeta.virtualProperties) {
999
- inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
1064
+ inputs.push(...cmpMeta.virtualProperties.map((prop) => mapInputProp(prop)));
1000
1065
  }
1001
1066
  const orderedInputs = sortBy(inputs, (cip) => cip.name);
1002
1067
  const methods = [];
@@ -1006,7 +1071,7 @@ ${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
1006
1071
  const inlineComponentProps = outputTarget.inlineProperties ? internalProps : [];
1007
1072
  const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, orderedInputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps, cmpMeta.events || []);
1008
1073
  const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
1009
- const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
1074
+ const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir, usesExportMaps(config));
1010
1075
  const proxyFileOutput = [componentDefinition, '\n'];
1011
1076
  if (includeSingleComponentAngularModules) {
1012
1077
  proxyFileOutput.push(moduleDefinition, '\n');
@@ -1037,6 +1102,12 @@ function generateBarrelFile(components, outputTarget) {
1037
1102
  }
1038
1103
  function getPathToComponentTypes(config, outputTarget) {
1039
1104
  const basePkg = outputTarget.componentCorePackage || '';
1105
+ // The entry of the output the proxies are built on always exports the component types - the
1106
+ // package root only does when the project has no `src/index.ts`, or that file re-exports them
1107
+ if (usesExportMaps(config)) {
1108
+ const entry = isOutputTypeCustomElementsBuild(outputTarget.outputType) ? 'standalone' : 'loader';
1109
+ return `${normalizePath(basePkg)}/${entry}`;
1110
+ }
1040
1111
  // in v5, all types (including components.d.ts) are generated in the dist/types directory
1041
1112
  const typesTarget = config.outputTargets?.find((o) => o.type === 'types');
1042
1113
  if (typesTarget) {
@@ -1,5 +1,11 @@
1
1
  import type { CompilerCtx, ComponentCompilerMeta, Config } from '@stencil/core/internal';
2
2
  import type { OutputTargetAngular, PackageJSON } from './types';
3
+ /**
4
+ * Whether the Stencil project generates a package.json `exports` map. Deep `dist/` imports are
5
+ * blocked for consumers then, so the proxies import through the map's entries instead
6
+ * (`<pkg>/<tag>`, `<pkg>/loader`, `<pkg>/standalone`).
7
+ */
8
+ export declare function usesExportMaps(config: Config): boolean;
3
9
  export declare function angularDirectiveProxyOutput(compilerCtx: CompilerCtx, outputTarget: OutputTargetAngular, components: ComponentCompilerMeta[], config: Config): Promise<void>;
4
10
  export declare function generateProxies(components: ComponentCompilerMeta[], pkgData: PackageJSON, outputTarget: OutputTargetAngular, rootDir: string, config: Config): string;
5
11
  /**
@@ -3,11 +3,78 @@ import { fileURLToPath } from 'url';
3
3
  const __filename = fileURLToPath(import.meta.url);
4
4
  const __dirname = path.dirname(__filename);
5
5
  import { relativeImport, normalizePath, sortBy, readPackageJson, dashToPascalCase, createImportStatement, isOutputTypeCustomElementsBuild, OutputTypes, mapPropName, } from './utils';
6
- import { createAngularComponentDefinition, createComponentTypeDefinition } from './generate-angular-component';
6
+ import { createAngularComponentDefinition, createComponentTypeDefinition, INPUT_TRANSFORM_FUNCTION, } from './generate-angular-component';
7
7
  import { generateAngularDirectivesFile } from './generate-angular-directives-file';
8
8
  import generateValueAccessors from './generate-value-accessors';
9
9
  import { generateAngularModuleForComponent } from './generate-angular-modules';
10
10
  import { generateTransformTagScript } from './generate-transformtag-script';
11
+ const filterInternalProps = (prop) => !prop.internal;
12
+ /**
13
+ * Whether a property declares an input transform.
14
+ *
15
+ * This is the single definition of the rule. Both the generated declaration and the import that
16
+ * satisfies it are derived from it, so widening the rule can't leave a generated file referencing
17
+ * a transform it never imports.
18
+ *
19
+ * @param prop The property compiler metadata.
20
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
21
+ * @returns `true` when the property should be transformed.
22
+ */
23
+ const isTransformedProp = (prop, booleanAttributes) => booleanAttributes && prop.type === 'boolean';
24
+ /**
25
+ * Maps a Stencil property to an Angular input declaration.
26
+ *
27
+ * Virtual properties are mapped with `booleanAttributes` left at its default of `false`. They
28
+ * carry a free-form type string and no `required` flag, so they are never transformed and always
29
+ * come out as optional.
30
+ *
31
+ * @param prop The property compiler metadata.
32
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
33
+ * @returns The Angular input declaration.
34
+ */
35
+ const mapInputProp = (prop, booleanAttributes = false) => ({
36
+ name: prop.name,
37
+ required: prop.required ?? false,
38
+ transform: isTransformedProp(prop, booleanAttributes) ? true : undefined,
39
+ });
40
+ /**
41
+ * Whether any of a component's properties will declare an input transform, which determines
42
+ * whether the transform needs to be imported.
43
+ *
44
+ * @param components The components in the generated file.
45
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
46
+ * @returns `true` when at least one property is transformed.
47
+ */
48
+ const usesInputTransform = (components, booleanAttributes) => components.some((cmpMeta) => (cmpMeta.properties ?? []).filter(filterInternalProps).some((prop) => isTransformedProp(prop, booleanAttributes)));
49
+ /**
50
+ * Whether the Stencil project generates a package.json `exports` map. Deep `dist/` imports are
51
+ * blocked for consumers then, so the proxies import through the map's entries instead
52
+ * (`<pkg>/<tag>`, `<pkg>/loader`, `<pkg>/standalone`).
53
+ */
54
+ export function usesExportMaps(config) {
55
+ // Stencil v5 only: v4's map has no `./standalone` entry, and its root types aren't
56
+ // guaranteed to export the component types.
57
+ const isV5 = (config.outputTargets || []).some((o) => ['loader-bundle', 'standalone', 'ssr', 'types'].includes(o.type));
58
+ return isV5 && config.generateExportMaps === true;
59
+ }
60
+ /**
61
+ * The module specifier a proxy imports a component's `defineCustomElement` from.
62
+ */
63
+ function getComponentModule(config, outputTarget, tagName) {
64
+ const basePkg = normalizePath(outputTarget.componentCorePackage);
65
+ if (usesExportMaps(config)) {
66
+ return `${basePkg}/${tagName}`;
67
+ }
68
+ return `${basePkg}/${outputTarget.customElementsDir}/${tagName}.js`;
69
+ }
70
+ /**
71
+ * The relative import of the project's own `components.d.ts`, for when there's no
72
+ * `componentCorePackage`. Only resolved when needed: package.json may have no `types` field.
73
+ */
74
+ function getComponentsTypeFile(pkgData, outputTarget, rootDir) {
75
+ const dtsFilePath = path.join(rootDir, path.dirname(pkgData.types), GENERATED_DTS);
76
+ return relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
77
+ }
11
78
  export async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
12
79
  const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
13
80
  const rootDir = config.rootDir;
@@ -41,27 +108,7 @@ export async function angularDirectiveProxyOutput(compilerCtx, outputTarget, com
41
108
  }
42
109
  // Generate transformer script if transformTag is enabled
43
110
  if (outputTarget.transformTag) {
44
- // Read the Angular library's package.json to get its name
45
- // directivesProxyFile is like: projects/library/src/directives/proxies.ts
46
- // We need to go up to: projects/library/package.json
47
- const angularLibraryDir = path.dirname(path.dirname(path.dirname(outputTarget.directivesProxyFile)));
48
- const angularPkgJsonPath = path.join(angularLibraryDir, 'package.json');
49
- let angularPackageName = '';
50
- try {
51
- const angularPkgJson = JSON.parse(await compilerCtx.fs.readFile(angularPkgJsonPath));
52
- if (angularPkgJson.name) {
53
- angularPackageName = angularPkgJson.name;
54
- }
55
- }
56
- catch (e) {
57
- throw new Error(`Could not read Angular library package.json at ${angularPkgJsonPath}. ` +
58
- `The package name is required to generate the transformTag patch script.`);
59
- }
60
- if (!angularPackageName) {
61
- throw new Error(`Angular library package.json at ${angularPkgJsonPath} does not have a "name" field. ` +
62
- `The package name is required to generate the transformTag patch script.`);
63
- }
64
- tasks.push(generateTransformTagScript(compilerCtx, filteredComponents, outputTarget, angularPackageName));
111
+ tasks.push(generateTransformTagScript(compilerCtx, filteredComponents, outputTarget, usesExportMaps(config)));
65
112
  }
66
113
  await Promise.all(tasks);
67
114
  }
@@ -85,10 +132,7 @@ async function copyResources(config, outputTarget) {
85
132
  ], srcDirectory);
86
133
  }
87
134
  export function generateProxies(components, pkgData, outputTarget, rootDir, config) {
88
- const distTypesDir = path.dirname(pkgData.types);
89
- const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
90
135
  const { outputType } = outputTarget;
91
- const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
92
136
  const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
93
137
  const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputType);
94
138
  const isStandaloneBuild = outputType === OutputTypes.Standalone;
@@ -105,6 +149,14 @@ export function generateProxies(components, pkgData, outputTarget, rootDir, conf
105
149
  * The collection of named imports from the angular-component-lib/utils.
106
150
  */
107
151
  const componentLibImports = ['ProxyCmp'];
152
+ /**
153
+ * The input transform lives in its own module so that it carries no runtime imports.
154
+ */
155
+ let transformImport = '';
156
+ const booleanAttributes = outputTarget.booleanAttributes === true;
157
+ if (usesInputTransform(components, booleanAttributes)) {
158
+ transformImport = `\n${createImportStatement([INPUT_TRANSFORM_FUNCTION], './angular-component-lib/boolean-attribute')}`;
159
+ }
108
160
  if (includeSingleComponentAngularModules) {
109
161
  angularCoreImports.push('NgModule');
110
162
  }
@@ -112,7 +164,7 @@ export function generateProxies(components, pkgData, outputTarget, rootDir, conf
112
164
  /* auto-generated angular directive proxies */
113
165
  ${createImportStatement(angularCoreImports, '@angular/core')}
114
166
 
115
- ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`;
167
+ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}${transformImport}\n`;
116
168
  /**
117
169
  * Generate JSX import type from correct location.
118
170
  * When using custom elements build, we need to import from
@@ -122,7 +174,7 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
122
174
  const generateTypeImports = () => {
123
175
  const importLocation = outputTarget.componentCorePackage
124
176
  ? getPathToComponentTypes(config, outputTarget)
125
- : normalizePath(componentsTypeFile);
177
+ : normalizePath(getComponentsTypeFile(pkgData, outputTarget, rootDir));
126
178
  return `import ${isCustomElementsBuild ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
127
179
  };
128
180
  const typeImports = generateTypeImports();
@@ -136,17 +188,11 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
136
188
  if (isCustomElementsBuild && outputTarget.componentCorePackage !== undefined) {
137
189
  const cmpImports = components.map((component) => {
138
190
  const pascalImport = dashToPascalCase(component.tagName);
139
- return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir}/${component.tagName}.js';`;
191
+ return `import { defineCustomElement as define${pascalImport} } from '${getComponentModule(config, outputTarget, component.tagName)}';`;
140
192
  });
141
193
  sourceImports = cmpImports.join('\n');
142
194
  }
143
195
  const proxyFileOutput = [];
144
- const filterInternalProps = (prop) => !prop.internal;
145
- // Ensure that virtual properties has required as false.
146
- const mapInputProp = (prop) => ({
147
- name: prop.name,
148
- required: prop.required ?? false,
149
- });
150
196
  const { componentCorePackage, customElementsDir } = outputTarget;
151
197
  for (let cmpMeta of components) {
152
198
  const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
@@ -154,9 +200,9 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
154
200
  if (cmpMeta.properties) {
155
201
  internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
156
202
  }
157
- const inputs = internalProps.map(mapInputProp);
203
+ const inputs = internalProps.map((prop) => mapInputProp(prop, booleanAttributes));
158
204
  if (cmpMeta.virtualProperties) {
159
- inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
205
+ inputs.push(...cmpMeta.virtualProperties.map((prop) => mapInputProp(prop)));
160
206
  }
161
207
  const orderedInputs = sortBy(inputs, (cip) => cip.name);
162
208
  const methods = [];
@@ -172,7 +218,7 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
172
218
  */
173
219
  const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, orderedInputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps, cmpMeta.events || []);
174
220
  const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
175
- const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
221
+ const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir, usesExportMaps(config));
176
222
  proxyFileOutput.push(componentDefinition, '\n');
177
223
  if (includeSingleComponentAngularModules) {
178
224
  proxyFileOutput.push(moduleDefinition, '\n');
@@ -187,9 +233,6 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
187
233
  */
188
234
  export function generateComponentProxy(cmpMeta, pkgData, outputTarget, rootDir, config) {
189
235
  const { outputType, componentCorePackage, customElementsDir } = outputTarget;
190
- const distTypesDir = path.dirname(pkgData.types);
191
- const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
192
- const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
193
236
  const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
194
237
  const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputType);
195
238
  const isStandaloneBuild = outputType === OutputTypes.Standalone;
@@ -203,34 +246,36 @@ export function generateComponentProxy(cmpMeta, pkgData, outputTarget, rootDir,
203
246
  if (includeSingleComponentAngularModules) {
204
247
  angularCoreImports.push('NgModule');
205
248
  }
249
+ const booleanAttributes = outputTarget.booleanAttributes === true;
250
+ const componentLibImports = ['ProxyCmp'];
251
+ // The input transform lives in its own module so that it carries no runtime imports.
252
+ let transformImport = '';
253
+ if (usesInputTransform([cmpMeta], booleanAttributes)) {
254
+ transformImport = `\n${createImportStatement([INPUT_TRANSFORM_FUNCTION], './angular-component-lib/boolean-attribute')}`;
255
+ }
206
256
  const imports = `/* tslint:disable */
207
257
  /* auto-generated angular directive proxies */
208
258
  ${createImportStatement(angularCoreImports, '@angular/core')}
209
259
 
210
- ${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
260
+ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}${transformImport}\n`;
211
261
  // Type imports
212
262
  const importLocation = componentCorePackage
213
263
  ? getPathToComponentTypes(config, outputTarget)
214
- : normalizePath(componentsTypeFile);
264
+ : normalizePath(getComponentsTypeFile(pkgData, outputTarget, rootDir));
215
265
  const typeImports = `import ${isCustomElementsBuild ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
216
266
  // defineCustomElement import
217
267
  let sourceImport = '';
218
268
  if (isCustomElementsBuild && componentCorePackage !== undefined) {
219
- sourceImport = `import { defineCustomElement as define${tagNameAsPascal} } from '${normalizePath(componentCorePackage)}/${customElementsDir}/${cmpMeta.tagName}.js';\n`;
269
+ sourceImport = `import { defineCustomElement as define${tagNameAsPascal} } from '${getComponentModule(config, outputTarget, cmpMeta.tagName)}';\n`;
220
270
  }
221
271
  // Generate component definition
222
- const filterInternalProps = (prop) => !prop.internal;
223
- const mapInputProp = (prop) => ({
224
- name: prop.name,
225
- required: prop.required ?? false,
226
- });
227
272
  const internalProps = [];
228
273
  if (cmpMeta.properties) {
229
274
  internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
230
275
  }
231
- const inputs = internalProps.map(mapInputProp);
276
+ const inputs = internalProps.map((prop) => mapInputProp(prop, booleanAttributes));
232
277
  if (cmpMeta.virtualProperties) {
233
- inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
278
+ inputs.push(...cmpMeta.virtualProperties.map((prop) => mapInputProp(prop)));
234
279
  }
235
280
  const orderedInputs = sortBy(inputs, (cip) => cip.name);
236
281
  const methods = [];
@@ -240,7 +285,7 @@ ${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
240
285
  const inlineComponentProps = outputTarget.inlineProperties ? internalProps : [];
241
286
  const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, orderedInputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps, cmpMeta.events || []);
242
287
  const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
243
- const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
288
+ const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir, usesExportMaps(config));
244
289
  const proxyFileOutput = [componentDefinition, '\n'];
245
290
  if (includeSingleComponentAngularModules) {
246
291
  proxyFileOutput.push(moduleDefinition, '\n');
@@ -271,6 +316,12 @@ export function generateBarrelFile(components, outputTarget) {
271
316
  }
272
317
  export function getPathToComponentTypes(config, outputTarget) {
273
318
  const basePkg = outputTarget.componentCorePackage || '';
319
+ // The entry of the output the proxies are built on always exports the component types - the
320
+ // package root only does when the project has no `src/index.ts`, or that file re-exports them
321
+ if (usesExportMaps(config)) {
322
+ const entry = isOutputTypeCustomElementsBuild(outputTarget.outputType) ? 'standalone' : 'loader';
323
+ return `${normalizePath(basePkg)}/${entry}`;
324
+ }
274
325
  // in v5, all types (including components.d.ts) are generated in the dist/types directory
275
326
  const typesTarget = config.outputTargets?.find((o) => o.type === 'types');
276
327
  if (typesTarget) {
package/dist/plugin.d.ts CHANGED
@@ -1,4 +1,9 @@
1
- import type { Config, OutputTargetCustom } from '@stencil/core/internal';
1
+ import type { Config } from '@stencil/core';
2
2
  import type { OutputTargetAngular } from './types';
3
+ type OutputTargetCustom = Extract<NonNullable<Config['outputTargets']>[number], {
4
+ type: 'custom';
5
+ }>;
6
+ type PluginConfig = Parameters<OutputTargetCustom['generator']>[0];
3
7
  export declare const angularOutputTarget: (outputTarget: OutputTargetAngular) => OutputTargetCustom;
4
- export declare function normalizeOutputTarget(config: Config, outputTarget: OutputTargetAngular): OutputTargetAngular;
8
+ export declare function normalizeOutputTarget(config: PluginConfig, outputTarget: OutputTargetAngular): OutputTargetAngular;
9
+ export {};