@stencil/angular-output-target 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generate-angular-component.d.ts +2 -2
- package/dist/generate-angular-component.js +17 -8
- package/dist/generate-transformtag-script.d.ts +8 -0
- package/dist/generate-transformtag-script.js +307 -0
- package/dist/index.cjs.js +352 -19
- package/dist/index.js +352 -19
- package/dist/output-angular.js +30 -11
- package/dist/types.d.ts +36 -0
- package/package.json +2 -2
- package/resources/control-value-accessors/boolean-value-accessor.ts +2 -2
- package/resources/control-value-accessors/number-value-accessor.ts +2 -2
- package/resources/control-value-accessors/radio-value-accessor.ts +2 -2
- package/resources/control-value-accessors/select-value-accessor.ts +2 -2
- package/resources/control-value-accessors/text-value-accessor.ts +2 -2
package/dist/index.js
CHANGED
|
@@ -186,15 +186,16 @@ function formatInputs(inputs) {
|
|
|
186
186
|
*
|
|
187
187
|
* @param tagName The tag name of the component.
|
|
188
188
|
* @param inputs The inputs of the Stencil component (e.g. [{name: 'myInput', required: true]).
|
|
189
|
-
* @param outputs The outputs/events of the Stencil component. (e.g. ['myOutput']).
|
|
190
189
|
* @param methods The methods of the Stencil component. (e.g. ['myMethod']).
|
|
191
190
|
* @param includeImportCustomElements Whether to define the component as a custom element.
|
|
192
191
|
* @param standalone Whether to define the component as a standalone component.
|
|
193
192
|
* @param inlineComponentProps List of properties that should be inlined into the component definition.
|
|
193
|
+
* @param events The events of the Stencil component for generating outputs.
|
|
194
194
|
* @returns The component declaration as a string.
|
|
195
195
|
*/
|
|
196
|
-
const createAngularComponentDefinition = (tagName, inputs,
|
|
196
|
+
const createAngularComponentDefinition = (tagName, inputs, methods, includeImportCustomElements = false, standalone = false, inlineComponentProps = [], events = []) => {
|
|
197
197
|
const tagNameAsPascal = dashToPascalCase(tagName);
|
|
198
|
+
const outputs = events.filter((event) => !event.internal).map((event) => event.name);
|
|
198
199
|
const hasInputs = inputs.length > 0;
|
|
199
200
|
const hasOutputs = outputs.length > 0;
|
|
200
201
|
const hasMethods = methods.length > 0;
|
|
@@ -223,7 +224,18 @@ const createAngularComponentDefinition = (tagName, inputs, outputs, methods, inc
|
|
|
223
224
|
standaloneOption = `\n standalone: false`;
|
|
224
225
|
}
|
|
225
226
|
const propertyDeclarations = inlineComponentProps.map((m) => createPropertyDeclaration(m, `Components.${tagNameAsPascal}['${m.name}']`, true));
|
|
226
|
-
const
|
|
227
|
+
const outputDeclarations = events
|
|
228
|
+
.filter((event) => !event.internal)
|
|
229
|
+
.map((event) => {
|
|
230
|
+
const camelCaseOutput = event.name.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
|
|
231
|
+
const outputType = `EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>`;
|
|
232
|
+
return `@Output() ${camelCaseOutput} = new ${outputType}();`;
|
|
233
|
+
});
|
|
234
|
+
const propertiesDeclarationText = [
|
|
235
|
+
`protected el: HTML${tagNameAsPascal}Element;`,
|
|
236
|
+
...propertyDeclarations,
|
|
237
|
+
...outputDeclarations,
|
|
238
|
+
].join('\n ');
|
|
227
239
|
/**
|
|
228
240
|
* Notes on the generated output:
|
|
229
241
|
* - We disable @angular-eslint/no-inputs-metadata-property, so that
|
|
@@ -237,16 +249,13 @@ const createAngularComponentDefinition = (tagName, inputs, outputs, methods, inc
|
|
|
237
249
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
238
250
|
template: '<ng-content></ng-content>',
|
|
239
251
|
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
|
240
|
-
inputs: [${formattedInputs}],${standaloneOption}
|
|
252
|
+
inputs: [${formattedInputs}],${hasOutputs ? `\n outputs: [${formattedOutputs}],` : ''}${standaloneOption}
|
|
241
253
|
})
|
|
242
254
|
export class ${tagNameAsPascal} {
|
|
243
255
|
${propertiesDeclarationText}
|
|
244
256
|
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
|
245
257
|
c.detach();
|
|
246
|
-
this.el = r.nativeElement
|
|
247
|
-
? `
|
|
248
|
-
proxyOutputs(this, this.el, [${formattedOutputs}]);`
|
|
249
|
-
: ''}
|
|
258
|
+
this.el = r.nativeElement;
|
|
250
259
|
}
|
|
251
260
|
}`;
|
|
252
261
|
return output;
|
|
@@ -444,17 +453,348 @@ export class ${moduleClassName} { }`;
|
|
|
444
453
|
return moduleDefinition;
|
|
445
454
|
};
|
|
446
455
|
|
|
456
|
+
/**
|
|
457
|
+
* Generates the patch-transform-selectors.mjs script for Angular transformTag support.
|
|
458
|
+
* This script patches component selectors in the built Angular library to use the
|
|
459
|
+
* transformed tag names (e.g., 'my-component' -> 'v1-my-component').
|
|
460
|
+
*/
|
|
461
|
+
async function generateTransformTagScript(compilerCtx, components, outputTarget, packageName) {
|
|
462
|
+
const scriptsDirectory = path.join(path.dirname(outputTarget.directivesProxyFile), '../../scripts');
|
|
463
|
+
const customElementsDir = outputTarget.customElementsDir || 'dist/components';
|
|
464
|
+
const stencilImportPath = `${outputTarget.componentCorePackage}/${customElementsDir}/index.js`;
|
|
465
|
+
// Generate the mappings object
|
|
466
|
+
const mappings = components
|
|
467
|
+
.map((component) => {
|
|
468
|
+
const tagName = component.tagName;
|
|
469
|
+
const pascalName = dashToPascalCase(tagName);
|
|
470
|
+
return ` '${tagName}': '${pascalName}'`;
|
|
471
|
+
})
|
|
472
|
+
.join(',\n');
|
|
473
|
+
// Generate selector patcher script
|
|
474
|
+
const patchSelectorsContent = `#!/usr/bin/env node
|
|
475
|
+
/* eslint-disable */
|
|
476
|
+
/* tslint:disable */
|
|
477
|
+
/**
|
|
478
|
+
* Selector Patcher for transformTag support
|
|
479
|
+
*
|
|
480
|
+
* AUTO-GENERATED - DO NOT EDIT
|
|
481
|
+
*
|
|
482
|
+
* This script patches @Component selectors in the installed Angular component library
|
|
483
|
+
* to match your runtime tag transformer. Run this as a postinstall script in your app.
|
|
484
|
+
*
|
|
485
|
+
* Usage Option 1 - Config file (recommended for complex transformers):
|
|
486
|
+
* Create tag-transformer.config.mjs in your app root:
|
|
487
|
+
* export default (tag) => {
|
|
488
|
+
* if (tag.startsWith('my-transform-')) return \`v1-\${tag}\`;
|
|
489
|
+
* // ... complex logic
|
|
490
|
+
* return tag;
|
|
491
|
+
* };
|
|
492
|
+
*
|
|
493
|
+
* Then in package.json:
|
|
494
|
+
* "scripts": {
|
|
495
|
+
* "postinstall": "patch-transform-selectors"
|
|
496
|
+
* }
|
|
497
|
+
*
|
|
498
|
+
* Usage Option 2 - CLI argument (for simple transformers):
|
|
499
|
+
* "scripts": {
|
|
500
|
+
* "postinstall": "patch-transform-selectors \\"(tag) => tag.startsWith('my-transform-') ? \\\\\`v1-\\\${tag}\\\\\` : tag\\""
|
|
501
|
+
* }
|
|
502
|
+
*/
|
|
503
|
+
|
|
504
|
+
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync } from 'fs';
|
|
505
|
+
import { join, dirname } from 'path';
|
|
506
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
507
|
+
|
|
508
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
509
|
+
const __dirname = dirname(__filename);
|
|
510
|
+
|
|
511
|
+
// Try to load transformer from config file or CLI argument
|
|
512
|
+
let TAG_TRANSFORMER;
|
|
513
|
+
let transformerArg;
|
|
514
|
+
|
|
515
|
+
// Option 1: Look for tag-transformer.config.mjs in the consuming app
|
|
516
|
+
const configPath = join(process.cwd(), 'tag-transformer.config.mjs');
|
|
517
|
+
if (existsSync(configPath)) {
|
|
518
|
+
console.log('[TransformTag] Loading transformer from tag-transformer.config.mjs');
|
|
519
|
+
try {
|
|
520
|
+
const configUrl = pathToFileURL(configPath).href;
|
|
521
|
+
const config = await import(configUrl);
|
|
522
|
+
TAG_TRANSFORMER = config.default;
|
|
523
|
+
|
|
524
|
+
if (typeof TAG_TRANSFORMER !== 'function') {
|
|
525
|
+
throw new Error('Config file must export a default function');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Store as string for injection later
|
|
529
|
+
transformerArg = TAG_TRANSFORMER.toString();
|
|
530
|
+
console.log('[TransformTag] Loaded transformer from config file');
|
|
531
|
+
} catch (error) {
|
|
532
|
+
console.error('[TransformTag] Error loading tag-transformer.config.mjs:', error.message);
|
|
533
|
+
console.error('Make sure the file exports a default function.');
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
} else {
|
|
537
|
+
// Option 2: Fall back to CLI argument
|
|
538
|
+
transformerArg = process.argv[2];
|
|
539
|
+
|
|
540
|
+
if (!transformerArg) {
|
|
541
|
+
console.error('[TransformTag] Error: No transformer provided.');
|
|
542
|
+
console.error('');
|
|
543
|
+
console.error('Option 1 - Create tag-transformer.config.mjs in your app root:');
|
|
544
|
+
console.error(' export default (tag) => tag.startsWith(\\'my-\\') ? \`v1-\${tag}\` : tag;');
|
|
545
|
+
console.error('');
|
|
546
|
+
console.error('Option 2 - Pass transformer as CLI argument:');
|
|
547
|
+
console.error(' patch-transform-selectors "(tag) => tag.startsWith(\\'my-\\') ? \`v1-\${tag}\` : tag"');
|
|
548
|
+
process.exit(1);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Evaluate the transformer string to get the function
|
|
552
|
+
try {
|
|
553
|
+
TAG_TRANSFORMER = eval(transformerArg);
|
|
554
|
+
if (typeof TAG_TRANSFORMER !== 'function') {
|
|
555
|
+
throw new Error('Transformer must be a function');
|
|
556
|
+
}
|
|
557
|
+
console.log('[TransformTag] Using transformer from CLI argument');
|
|
558
|
+
} catch (error) {
|
|
559
|
+
console.error('[TransformTag] Error: Invalid transformer function:', error.message);
|
|
560
|
+
console.error('The transformer must be a valid JavaScript function expression.');
|
|
561
|
+
console.error('Example: "(tag) => tag.startsWith(\\'my-\\') ? \`v1-\${tag}\` : tag"');
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const TAG_MAPPINGS = {
|
|
567
|
+
${mappings}
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
console.log('[TransformTag] Patching component selectors...');
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
// Find the bundled JavaScript file (could be fesm2022, fesm2015, fesm5, etc.)
|
|
574
|
+
const parentDir = join(__dirname, '..');
|
|
575
|
+
|
|
576
|
+
// Find all .js/.mjs files in fesm* directories AND fesm*.js/mjs files at root
|
|
577
|
+
let bundlePaths = [];
|
|
578
|
+
|
|
579
|
+
try {
|
|
580
|
+
const entries = readdirSync(parentDir);
|
|
581
|
+
for (const entry of entries) {
|
|
582
|
+
const entryPath = join(parentDir, entry);
|
|
583
|
+
let stat;
|
|
584
|
+
try {
|
|
585
|
+
stat = statSync(entryPath);
|
|
586
|
+
} catch (e) {
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Check for fesm* directories
|
|
591
|
+
if (stat.isDirectory() && /^fesm/.test(entry)) {
|
|
592
|
+
try {
|
|
593
|
+
const fesmFiles = readdirSync(entryPath);
|
|
594
|
+
for (const file of fesmFiles) {
|
|
595
|
+
if (/\\.m?js$/.test(file)) {
|
|
596
|
+
bundlePaths.push(join(entryPath, file));
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
} catch (e) {
|
|
600
|
+
// Skip if can't read fesm directory
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// Check for fesm*.js or fesm*.mjs files at root
|
|
604
|
+
else if (stat.isFile() && /^fesm.*\\.m?js$/.test(entry)) {
|
|
605
|
+
bundlePaths.push(entryPath);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
} catch (e) {
|
|
609
|
+
console.error('[TransformTag] Could not read parent directory:', parentDir);
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (bundlePaths.length === 0) {
|
|
614
|
+
console.error('[TransformTag] Could not find any fesm* directories or files to patch.');
|
|
615
|
+
process.exit(1);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
console.log('[TransformTag] Found bundles:', bundlePaths);
|
|
619
|
+
|
|
620
|
+
// Patch all bundled JavaScript files
|
|
621
|
+
let totalPatchedCount = 0;
|
|
622
|
+
|
|
623
|
+
for (const bundlePath of bundlePaths) {
|
|
624
|
+
let bundleContent;
|
|
625
|
+
try {
|
|
626
|
+
bundleContent = readFileSync(bundlePath, 'utf8');
|
|
627
|
+
} catch (e) {
|
|
628
|
+
console.error('[TransformTag] Could not read bundle:', bundlePath);
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
let patchedCount = 0;
|
|
633
|
+
|
|
634
|
+
for (const [originalTag, pascalName] of Object.entries(TAG_MAPPINGS)) {
|
|
635
|
+
const transformedTag = TAG_TRANSFORMER(originalTag);
|
|
636
|
+
|
|
637
|
+
// Only patch if the tag is actually transformed
|
|
638
|
+
if (transformedTag !== originalTag) {
|
|
639
|
+
// Update selector from original tag name to transformed tag name
|
|
640
|
+
// e.g., selector: 'my-transform-test' becomes selector: 'v1-my-transform-test'
|
|
641
|
+
const selectorRegex = new RegExp(
|
|
642
|
+
\`(selector:\\\\s*)(['"\\\`])\${originalTag}\\\\2\`,
|
|
643
|
+
'g'
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
const newContent = bundleContent.replace(
|
|
647
|
+
selectorRegex,
|
|
648
|
+
\`$1'\${transformedTag}'\`
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
if (newContent !== bundleContent) {
|
|
652
|
+
bundleContent = newContent;
|
|
653
|
+
patchedCount++;
|
|
654
|
+
console.log(\`[TransformTag] Patched selector for \${originalTag} -> \${transformedTag}\`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Inject setTagTransformer call with the user's transformer
|
|
660
|
+
// Find the export statement and add the call before it
|
|
661
|
+
const exportMatch = bundleContent.match(/export \\{ setTagTransformer/);
|
|
662
|
+
if (exportMatch && patchedCount > 0) {
|
|
663
|
+
const transformerCode = \`
|
|
664
|
+
// Auto-injected by patch-transform-selectors
|
|
665
|
+
// Call setTagTransformer with the user-provided transformer
|
|
666
|
+
import { setTagTransformer as stencilSetTagTransformer } from '${stencilImportPath}';
|
|
667
|
+
stencilSetTagTransformer(\${transformerArg});
|
|
668
|
+
\`;
|
|
669
|
+
bundleContent = transformerCode + bundleContent;
|
|
670
|
+
console.log('[TransformTag] Injected setTagTransformer call into bundle');
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Write the patched bundle
|
|
674
|
+
if (patchedCount > 0) {
|
|
675
|
+
writeFileSync(bundlePath, bundleContent);
|
|
676
|
+
totalPatchedCount += patchedCount;
|
|
677
|
+
console.log(\`[TransformTag] Successfully patched \${patchedCount} component selectors in \${bundlePath}\`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Find and patch all .d.ts files
|
|
682
|
+
let totalTypePatchedCount = 0;
|
|
683
|
+
|
|
684
|
+
function patchTypeDefsInDir(dir) {
|
|
685
|
+
let files;
|
|
686
|
+
try {
|
|
687
|
+
files = readdirSync(dir);
|
|
688
|
+
} catch (e) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
for (const file of files) {
|
|
693
|
+
const filePath = join(dir, file);
|
|
694
|
+
let stat;
|
|
695
|
+
try {
|
|
696
|
+
stat = statSync(filePath);
|
|
697
|
+
} catch (e) {
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (stat.isDirectory()) {
|
|
702
|
+
patchTypeDefsInDir(filePath);
|
|
703
|
+
} else if (file.endsWith('.d.ts')) {
|
|
704
|
+
let typeDefsContent;
|
|
705
|
+
try {
|
|
706
|
+
typeDefsContent = readFileSync(filePath, 'utf8');
|
|
707
|
+
} catch (e) {
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
let modified = false;
|
|
712
|
+
|
|
713
|
+
for (const [originalTag, pascalName] of Object.entries(TAG_MAPPINGS)) {
|
|
714
|
+
const transformedTag = TAG_TRANSFORMER(originalTag);
|
|
715
|
+
|
|
716
|
+
if (transformedTag !== originalTag) {
|
|
717
|
+
// Update selector in type definitions - format: ɵɵComponentDeclaration<ClassName, "tag-name", ...>
|
|
718
|
+
const typeDefRegex = new RegExp(
|
|
719
|
+
\`(ɵɵComponentDeclaration<\${pascalName},\\\\s*)"(\${originalTag})"\`,
|
|
720
|
+
'g'
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
const newTypeContent = typeDefsContent.replace(
|
|
724
|
+
typeDefRegex,
|
|
725
|
+
\`$1"\${transformedTag}"\`
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
if (newTypeContent !== typeDefsContent) {
|
|
729
|
+
typeDefsContent = newTypeContent;
|
|
730
|
+
modified = true;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (modified) {
|
|
736
|
+
writeFileSync(filePath, typeDefsContent);
|
|
737
|
+
totalTypePatchedCount++;
|
|
738
|
+
console.log(\`[TransformTag] Patched type definitions in: \${filePath}\`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
patchTypeDefsInDir(parentDir);
|
|
745
|
+
|
|
746
|
+
if (totalTypePatchedCount > 0) {
|
|
747
|
+
console.log(\`[TransformTag] Successfully patched selectors in \${totalTypePatchedCount} type definition files.\`);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (totalPatchedCount === 0 && totalTypePatchedCount === 0) {
|
|
751
|
+
console.log('[TransformTag] No selectors needed patching.');
|
|
752
|
+
}
|
|
753
|
+
} catch (error) {
|
|
754
|
+
console.error('[TransformTag] Error patching selectors:', error.message);
|
|
755
|
+
console.error('Stack:', error.stack);
|
|
756
|
+
process.exit(1);
|
|
757
|
+
}
|
|
758
|
+
`;
|
|
759
|
+
await compilerCtx.fs.writeFile(path.join(scriptsDirectory, 'patch-transform-selectors.mjs'), patchSelectorsContent);
|
|
760
|
+
}
|
|
761
|
+
|
|
447
762
|
async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
|
|
448
763
|
const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
|
|
449
764
|
const rootDir = config.rootDir;
|
|
450
765
|
const pkgData = await readPackageJson(config, rootDir);
|
|
451
766
|
const finalText = generateProxies(filteredComponents, pkgData, outputTarget, config.rootDir);
|
|
452
|
-
|
|
767
|
+
const tasks = [
|
|
453
768
|
compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText),
|
|
454
769
|
copyResources(config, outputTarget),
|
|
455
770
|
generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget),
|
|
456
771
|
generateValueAccessors(compilerCtx, filteredComponents, outputTarget, config),
|
|
457
|
-
]
|
|
772
|
+
];
|
|
773
|
+
// Generate transformer script if transformTag is enabled
|
|
774
|
+
if (outputTarget.transformTag) {
|
|
775
|
+
// Read the Angular library's package.json to get its name
|
|
776
|
+
// directivesProxyFile is like: projects/library/src/directives/proxies.ts
|
|
777
|
+
// We need to go up to: projects/library/package.json
|
|
778
|
+
const angularLibraryDir = path.dirname(path.dirname(path.dirname(outputTarget.directivesProxyFile)));
|
|
779
|
+
const angularPkgJsonPath = path.join(angularLibraryDir, 'package.json');
|
|
780
|
+
let angularPackageName = '';
|
|
781
|
+
try {
|
|
782
|
+
const angularPkgJson = JSON.parse(await compilerCtx.fs.readFile(angularPkgJsonPath));
|
|
783
|
+
if (angularPkgJson.name) {
|
|
784
|
+
angularPackageName = angularPkgJson.name;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
catch (e) {
|
|
788
|
+
throw new Error(`Could not read Angular library package.json at ${angularPkgJsonPath}. ` +
|
|
789
|
+
`The package name is required to generate the transformTag patch script.`);
|
|
790
|
+
}
|
|
791
|
+
if (!angularPackageName) {
|
|
792
|
+
throw new Error(`Angular library package.json at ${angularPkgJsonPath} does not have a "name" field. ` +
|
|
793
|
+
`The package name is required to generate the transformTag patch script.`);
|
|
794
|
+
}
|
|
795
|
+
tasks.push(generateTransformTagScript(compilerCtx, filteredComponents, outputTarget));
|
|
796
|
+
}
|
|
797
|
+
await Promise.all(tasks);
|
|
458
798
|
}
|
|
459
799
|
function getFilteredComponents(excludeComponents = [], cmps) {
|
|
460
800
|
return sortBy(cmps, (cmp) => cmp.tagName).filter((c) => !excludeComponents.includes(c.tagName) && !c.internal);
|
|
@@ -489,16 +829,13 @@ function generateProxies(components, pkgData, outputTarget, rootDir) {
|
|
|
489
829
|
*/
|
|
490
830
|
const angularCoreImports = ['ChangeDetectionStrategy', 'ChangeDetectorRef', 'Component', 'ElementRef'];
|
|
491
831
|
if (includeOutputImports) {
|
|
492
|
-
angularCoreImports.push('EventEmitter');
|
|
832
|
+
angularCoreImports.push('EventEmitter', 'Output');
|
|
493
833
|
}
|
|
494
834
|
angularCoreImports.push('NgZone');
|
|
495
835
|
/**
|
|
496
836
|
* The collection of named imports from the angular-component-lib/utils.
|
|
497
837
|
*/
|
|
498
838
|
const componentLibImports = ['ProxyCmp'];
|
|
499
|
-
if (includeOutputImports) {
|
|
500
|
-
componentLibImports.push('proxyOutputs');
|
|
501
|
-
}
|
|
502
839
|
if (includeSingleComponentAngularModules) {
|
|
503
840
|
angularCoreImports.push('NgModule');
|
|
504
841
|
}
|
|
@@ -557,10 +894,6 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
|
|
|
557
894
|
inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
|
|
558
895
|
}
|
|
559
896
|
const orderedInputs = sortBy(inputs, (cip) => cip.name);
|
|
560
|
-
const outputs = [];
|
|
561
|
-
if (cmpMeta.events) {
|
|
562
|
-
outputs.push(...cmpMeta.events.filter(filterInternalProps).map(mapPropName));
|
|
563
|
-
}
|
|
564
897
|
const methods = [];
|
|
565
898
|
if (cmpMeta.methods) {
|
|
566
899
|
methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
|
|
@@ -572,7 +905,7 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
|
|
|
572
905
|
* 2. Optionally the @NgModule decorated class (if includeSingleComponentAngularModules is true)
|
|
573
906
|
* 3. The component interface (using declaration merging for types).
|
|
574
907
|
*/
|
|
575
|
-
const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, orderedInputs,
|
|
908
|
+
const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, orderedInputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps, cmpMeta.events || []);
|
|
576
909
|
const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
|
|
577
910
|
const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
|
|
578
911
|
proxyFileOutput.push(componentDefinition, '\n');
|
package/dist/output-angular.js
CHANGED
|
@@ -4,17 +4,43 @@ import { createAngularComponentDefinition, createComponentTypeDefinition } from
|
|
|
4
4
|
import { generateAngularDirectivesFile } from './generate-angular-directives-file';
|
|
5
5
|
import generateValueAccessors from './generate-value-accessors';
|
|
6
6
|
import { generateAngularModuleForComponent } from './generate-angular-modules';
|
|
7
|
+
import { generateTransformTagScript } from './generate-transformtag-script';
|
|
7
8
|
export async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
|
|
8
9
|
const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
|
|
9
10
|
const rootDir = config.rootDir;
|
|
10
11
|
const pkgData = await readPackageJson(config, rootDir);
|
|
11
12
|
const finalText = generateProxies(filteredComponents, pkgData, outputTarget, config.rootDir);
|
|
12
|
-
|
|
13
|
+
const tasks = [
|
|
13
14
|
compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText),
|
|
14
15
|
copyResources(config, outputTarget),
|
|
15
16
|
generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget),
|
|
16
17
|
generateValueAccessors(compilerCtx, filteredComponents, outputTarget, config),
|
|
17
|
-
]
|
|
18
|
+
];
|
|
19
|
+
// Generate transformer script if transformTag is enabled
|
|
20
|
+
if (outputTarget.transformTag) {
|
|
21
|
+
// Read the Angular library's package.json to get its name
|
|
22
|
+
// directivesProxyFile is like: projects/library/src/directives/proxies.ts
|
|
23
|
+
// We need to go up to: projects/library/package.json
|
|
24
|
+
const angularLibraryDir = path.dirname(path.dirname(path.dirname(outputTarget.directivesProxyFile)));
|
|
25
|
+
const angularPkgJsonPath = path.join(angularLibraryDir, 'package.json');
|
|
26
|
+
let angularPackageName = '';
|
|
27
|
+
try {
|
|
28
|
+
const angularPkgJson = JSON.parse(await compilerCtx.fs.readFile(angularPkgJsonPath));
|
|
29
|
+
if (angularPkgJson.name) {
|
|
30
|
+
angularPackageName = angularPkgJson.name;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
throw new Error(`Could not read Angular library package.json at ${angularPkgJsonPath}. ` +
|
|
35
|
+
`The package name is required to generate the transformTag patch script.`);
|
|
36
|
+
}
|
|
37
|
+
if (!angularPackageName) {
|
|
38
|
+
throw new Error(`Angular library package.json at ${angularPkgJsonPath} does not have a "name" field. ` +
|
|
39
|
+
`The package name is required to generate the transformTag patch script.`);
|
|
40
|
+
}
|
|
41
|
+
tasks.push(generateTransformTagScript(compilerCtx, filteredComponents, outputTarget, angularPackageName));
|
|
42
|
+
}
|
|
43
|
+
await Promise.all(tasks);
|
|
18
44
|
}
|
|
19
45
|
function getFilteredComponents(excludeComponents = [], cmps) {
|
|
20
46
|
return sortBy(cmps, (cmp) => cmp.tagName).filter((c) => !excludeComponents.includes(c.tagName) && !c.internal);
|
|
@@ -49,16 +75,13 @@ export function generateProxies(components, pkgData, outputTarget, rootDir) {
|
|
|
49
75
|
*/
|
|
50
76
|
const angularCoreImports = ['ChangeDetectionStrategy', 'ChangeDetectorRef', 'Component', 'ElementRef'];
|
|
51
77
|
if (includeOutputImports) {
|
|
52
|
-
angularCoreImports.push('EventEmitter');
|
|
78
|
+
angularCoreImports.push('EventEmitter', 'Output');
|
|
53
79
|
}
|
|
54
80
|
angularCoreImports.push('NgZone');
|
|
55
81
|
/**
|
|
56
82
|
* The collection of named imports from the angular-component-lib/utils.
|
|
57
83
|
*/
|
|
58
84
|
const componentLibImports = ['ProxyCmp'];
|
|
59
|
-
if (includeOutputImports) {
|
|
60
|
-
componentLibImports.push('proxyOutputs');
|
|
61
|
-
}
|
|
62
85
|
if (includeSingleComponentAngularModules) {
|
|
63
86
|
angularCoreImports.push('NgModule');
|
|
64
87
|
}
|
|
@@ -117,10 +140,6 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
|
|
|
117
140
|
inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
|
|
118
141
|
}
|
|
119
142
|
const orderedInputs = sortBy(inputs, (cip) => cip.name);
|
|
120
|
-
const outputs = [];
|
|
121
|
-
if (cmpMeta.events) {
|
|
122
|
-
outputs.push(...cmpMeta.events.filter(filterInternalProps).map(mapPropName));
|
|
123
|
-
}
|
|
124
143
|
const methods = [];
|
|
125
144
|
if (cmpMeta.methods) {
|
|
126
145
|
methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
|
|
@@ -132,7 +151,7 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
|
|
|
132
151
|
* 2. Optionally the @NgModule decorated class (if includeSingleComponentAngularModules is true)
|
|
133
152
|
* 3. The component interface (using declaration merging for types).
|
|
134
153
|
*/
|
|
135
|
-
const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, orderedInputs,
|
|
154
|
+
const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, orderedInputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps, cmpMeta.events || []);
|
|
136
155
|
const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
|
|
137
156
|
const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
|
|
138
157
|
proxyFileOutput.push(componentDefinition, '\n');
|
package/dist/types.d.ts
CHANGED
|
@@ -33,6 +33,42 @@ export interface OutputTargetAngular {
|
|
|
33
33
|
* to type-check and show jsdocs when using the components in html-templates.
|
|
34
34
|
*/
|
|
35
35
|
inlineProperties?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Use `transformTag` to generate a build-time script.
|
|
38
|
+
* This script patches Angular `@Component` selectors in any installed Angular wrapper library
|
|
39
|
+
* enabling run-time tag transformation. Run the script as a postinstall script in your app.
|
|
40
|
+
*
|
|
41
|
+
* Setup:
|
|
42
|
+
* 1. Export `transformTag` and `setTagTransformer` from your component library:
|
|
43
|
+
* ```ts
|
|
44
|
+
* // src/index.ts
|
|
45
|
+
* export { transformTag, setTagTransformer } from '@stencil/core';
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* 2. Add the patch script as a postinstall hook in your consuming app's package.json:
|
|
49
|
+
* ```json
|
|
50
|
+
* {
|
|
51
|
+
* "scripts": {
|
|
52
|
+
* "postinstall": "patch-transform-selectors \"(tag) => tag.startsWith('my-') ? `v1-${tag}` : tag\""
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* 3. Configure the transformer at runtime in your Angular app with the SAME transformer:
|
|
58
|
+
* ```ts
|
|
59
|
+
* // main.ts
|
|
60
|
+
* import { setTagTransformer } from 'component-library';
|
|
61
|
+
* setTagTransformer((tag) => tag.startsWith('my-') ? `v1-${tag}` : tag);
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* Angular relies on a static selector string so the patch script is used to
|
|
65
|
+
* modify the installed Angular component library for transformed tags.
|
|
66
|
+
* For example, if `my-button` transforms to `v1-my-button`, the selector
|
|
67
|
+
* will be patched from `selector: 'my-button'` to `selector: 'v1-my-button'`.
|
|
68
|
+
*
|
|
69
|
+
* @default false
|
|
70
|
+
*/
|
|
71
|
+
transformTag?: boolean;
|
|
36
72
|
}
|
|
37
73
|
export type ValueAccessorTypes = 'text' | 'radio' | 'select' | 'number' | 'boolean';
|
|
38
74
|
export interface ValueAccessorConfig {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stencil/angular-output-target",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Angular output target for @stencil/core components.",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@angular/core": "8.2.14",
|
|
52
52
|
"@angular/forms": "8.2.14",
|
|
53
|
-
"@stencil/core": "4.
|
|
53
|
+
"@stencil/core": "4.41.0",
|
|
54
54
|
"@types/node": "^18.0.0",
|
|
55
55
|
"npm-run-all2": "^6.2.4",
|
|
56
56
|
"rimraf": "^5.0.0",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Directive, ElementRef } from '@angular/core';
|
|
1
|
+
import { Directive, ElementRef, forwardRef } from '@angular/core';
|
|
2
2
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
3
3
|
|
|
4
4
|
import { ValueAccessor } from './value-accessor';
|
|
@@ -12,7 +12,7 @@ import { ValueAccessor } from './value-accessor';
|
|
|
12
12
|
providers: [
|
|
13
13
|
{
|
|
14
14
|
provide: NG_VALUE_ACCESSOR,
|
|
15
|
-
useExisting: BooleanValueAccessor,
|
|
15
|
+
useExisting: forwardRef(() => BooleanValueAccessor),
|
|
16
16
|
multi: true
|
|
17
17
|
}
|
|
18
18
|
]<VALUE_ACCESSOR_STANDALONE>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Directive, ElementRef } from '@angular/core';
|
|
1
|
+
import { Directive, ElementRef, forwardRef } from '@angular/core';
|
|
2
2
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
3
3
|
|
|
4
4
|
import { ValueAccessor } from './value-accessor';
|
|
@@ -12,7 +12,7 @@ import { ValueAccessor } from './value-accessor';
|
|
|
12
12
|
providers: [
|
|
13
13
|
{
|
|
14
14
|
provide: NG_VALUE_ACCESSOR,
|
|
15
|
-
useExisting: NumericValueAccessor,
|
|
15
|
+
useExisting: forwardRef(() => NumericValueAccessor),
|
|
16
16
|
multi: true
|
|
17
17
|
}
|
|
18
18
|
]<VALUE_ACCESSOR_STANDALONE>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Directive, ElementRef } from '@angular/core';
|
|
1
|
+
import { Directive, ElementRef, forwardRef } from '@angular/core';
|
|
2
2
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
3
3
|
|
|
4
4
|
import { ValueAccessor } from './value-accessor';
|
|
@@ -12,7 +12,7 @@ import { ValueAccessor } from './value-accessor';
|
|
|
12
12
|
providers: [
|
|
13
13
|
{
|
|
14
14
|
provide: NG_VALUE_ACCESSOR,
|
|
15
|
-
useExisting: RadioValueAccessor,
|
|
15
|
+
useExisting: forwardRef(() => RadioValueAccessor),
|
|
16
16
|
multi: true
|
|
17
17
|
}
|
|
18
18
|
]<VALUE_ACCESSOR_STANDALONE>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Directive, ElementRef } from '@angular/core';
|
|
1
|
+
import { Directive, ElementRef, forwardRef } from '@angular/core';
|
|
2
2
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
3
3
|
|
|
4
4
|
import { ValueAccessor } from './value-accessor';
|
|
@@ -12,7 +12,7 @@ import { ValueAccessor } from './value-accessor';
|
|
|
12
12
|
providers: [
|
|
13
13
|
{
|
|
14
14
|
provide: NG_VALUE_ACCESSOR,
|
|
15
|
-
useExisting: SelectValueAccessor,
|
|
15
|
+
useExisting: forwardRef(() => SelectValueAccessor),
|
|
16
16
|
multi: true
|
|
17
17
|
}
|
|
18
18
|
]<VALUE_ACCESSOR_STANDALONE>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Directive, ElementRef } from '@angular/core';
|
|
1
|
+
import { Directive, ElementRef, forwardRef } from '@angular/core';
|
|
2
2
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
3
3
|
|
|
4
4
|
import { ValueAccessor } from './value-accessor';
|
|
@@ -12,7 +12,7 @@ import { ValueAccessor } from './value-accessor';
|
|
|
12
12
|
providers: [
|
|
13
13
|
{
|
|
14
14
|
provide: NG_VALUE_ACCESSOR,
|
|
15
|
-
useExisting: TextValueAccessor,
|
|
15
|
+
useExisting: forwardRef(() => TextValueAccessor),
|
|
16
16
|
multi: true
|
|
17
17
|
}
|
|
18
18
|
]<VALUE_ACCESSOR_STANDALONE>
|