UNPKG

@stencil/angular-output-target

Version:

Angular output target for @stencil/core components.

142 lines (140 loc) 7.45 kB
import path from 'path'; import { relativeImport, normalizePath, sortBy, readPackageJson, dashToPascalCase, createImportStatement, isOutputTypeCustomElementsBuild, OutputTypes, } from './utils'; import { createAngularComponentDefinition, createComponentTypeDefinition } from './generate-angular-component'; import { generateAngularDirectivesFile } from './generate-angular-directives-file'; import generateValueAccessors from './generate-value-accessors'; import { generateAngularModuleForComponent } from './generate-angular-modules'; export async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) { const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components); const rootDir = config.rootDir; const pkgData = await readPackageJson(config, rootDir); const finalText = generateProxies(filteredComponents, pkgData, outputTarget, config.rootDir); await Promise.all([ compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText), copyResources(config, outputTarget), generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget), generateValueAccessors(compilerCtx, filteredComponents, outputTarget, config), ]); } function getFilteredComponents(excludeComponents = [], cmps) { return sortBy(cmps, (cmp) => cmp.tagName).filter((c) => !excludeComponents.includes(c.tagName) && !c.internal); } async function copyResources(config, outputTarget) { if (!config.sys || !config.sys.copy || !config.sys.glob) { throw new Error('stencil is not properly initialized at this step. Notify the developer'); } const srcDirectory = path.join(__dirname, '..', 'angular-component-lib'); const destDirectory = path.join(path.dirname(outputTarget.directivesProxyFile), 'angular-component-lib'); return config.sys.copy([ { src: srcDirectory, dest: destDirectory, keepDirStructure: false, warn: false, ignore: [], }, ], srcDirectory); } export function generateProxies(components, pkgData, outputTarget, rootDir) { const distTypesDir = path.dirname(pkgData.types); const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS); const { outputType } = outputTarget; const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts'); const includeSingleComponentAngularModules = outputType === OutputTypes.Scam; const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputType); const isStandaloneBuild = outputType === OutputTypes.Standalone; const includeOutputImports = components.some((component) => component.events.some((event) => !event.internal)); /** * The collection of named imports from @angular/core. */ const angularCoreImports = ['ChangeDetectionStrategy', 'ChangeDetectorRef', 'Component', 'ElementRef']; if (includeOutputImports) { angularCoreImports.push('EventEmitter'); } angularCoreImports.push('NgZone'); /** * The collection of named imports from the angular-component-lib/utils. */ const componentLibImports = ['ProxyCmp']; if (includeOutputImports) { componentLibImports.push('proxyOutputs'); } if (includeSingleComponentAngularModules) { angularCoreImports.push('NgModule'); } const imports = `/* tslint:disable */ /* auto-generated angular directive proxies */ ${createImportStatement(angularCoreImports, '@angular/core')} ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`; /** * Generate JSX import type from correct location. * When using custom elements build, we need to import from * either the "components" directory or customElementsDir * otherwise we risk bundlers pulling in lazy loaded imports. */ const generateTypeImports = () => { let importLocation = outputTarget.componentCorePackage ? normalizePath(outputTarget.componentCorePackage) : normalizePath(componentsTypeFile); importLocation += isCustomElementsBuild ? `/${outputTarget.customElementsDir}` : ''; return `import ${isCustomElementsBuild ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`; }; const typeImports = generateTypeImports(); let sourceImports = ''; /** * Build an array of Custom Elements build imports and namespace them * so that they do not conflict with the Angular wrapper names. For example, * IonButton would be imported as IonButtonCmp so as to not conflict with the * IonButton Angular Component that takes in the Web Component as a parameter. */ if (isCustomElementsBuild && outputTarget.componentCorePackage !== undefined) { const cmpImports = components.map((component) => { const pascalImport = dashToPascalCase(component.tagName); return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir}/${component.tagName}.js';`; }); sourceImports = cmpImports.join('\n'); } const proxyFileOutput = []; const filterInternalProps = (prop) => !prop.internal; const mapPropName = (prop) => prop.name; const { componentCorePackage, customElementsDir } = outputTarget; for (let cmpMeta of components) { const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName); const internalProps = []; if (cmpMeta.properties) { internalProps.push(...cmpMeta.properties.filter(filterInternalProps)); } const inputs = internalProps.map(mapPropName); if (cmpMeta.virtualProperties) { inputs.push(...cmpMeta.virtualProperties.map(mapPropName)); } inputs.sort(); const outputs = []; if (cmpMeta.events) { outputs.push(...cmpMeta.events.filter(filterInternalProps).map(mapPropName)); } const methods = []; if (cmpMeta.methods) { methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName)); } const inlineComponentProps = outputTarget.inlineProperties ? internalProps : []; /** * For each component, we need to generate: * 1. The @Component decorated class * 2. Optionally the @NgModule decorated class (if includeSingleComponentAngularModules is true) * 3. The component interface (using declaration merging for types). */ const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps); const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName); const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir); proxyFileOutput.push(componentDefinition, '\n'); if (includeSingleComponentAngularModules) { proxyFileOutput.push(moduleDefinition, '\n'); } proxyFileOutput.push(componentTypeDefinition, '\n'); } const final = [imports, typeImports, sourceImports, ...proxyFileOutput]; return final.join('\n') + '\n'; } const GENERATED_DTS = 'components.d.ts'; const IMPORT_TYPES = 'Components';