UNPKG

8.3 kBJavaScriptView Raw
1import path from 'path';
2import { dashToPascalCase, normalizePath, readPackageJson, relativeImport, sortBy } from './utils';
3/**
4 * Generate and write the Stencil-React bindings to disc
5 * @param config the Stencil configuration associated with the project
6 * @param compilerCtx the compiler context of the current Stencil build
7 * @param outputTarget the output target configuration for generating the React wrapper
8 * @param components the components to generate the bindings for
9 */
10export async function reactProxyOutput(config, compilerCtx, outputTarget, components) {
11 const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
12 const rootDir = config.rootDir;
13 const pkgData = await readPackageJson(rootDir);
14 const finalText = generateProxies(config, filteredComponents, pkgData, outputTarget, rootDir);
15 await compilerCtx.fs.writeFile(outputTarget.proxiesFile, finalText);
16 await copyResources(config, outputTarget);
17}
18/**
19 * Removes all components from the provided `cmps` list that exist in the provided `excludedComponents` list
20 * @param excludeComponents the list of components that should be removed from the provided `cmps` list
21 * @param cmps a list of components
22 * @returns the filtered list of components
23 */
24function getFilteredComponents(excludeComponents = [], cmps) {
25 return sortBy(cmps, (cmp) => cmp.tagName).filter((c) => !excludeComponents.includes(c.tagName) && !c.internal);
26}
27/**
28 * Generate the code that will be responsible for creating the Stencil-React bindings
29 * @param config the Stencil configuration associated with the project
30 * @param components the Stencil components to generate wrappers for
31 * @param pkgData `package.json` data for the Stencil project
32 * @param outputTarget the output target configuration used to generate the Stencil-React bindings
33 * @param rootDir the directory of the Stencil project
34 * @returns the generated code to create the Stencil-React bindings
35 */
36export function generateProxies(config, components, pkgData, outputTarget, rootDir) {
37 const distTypesDir = path.dirname(pkgData.types);
38 const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
39 const componentsTypeFile = relativeImport(outputTarget.proxiesFile, dtsFilePath, '.d.ts');
40 const pathToCorePackageLoader = getPathToCorePackageLoader(config, outputTarget);
41 const imports = `/* eslint-disable */
42/* tslint:disable */
43/* auto-generated react proxies */
44import { createReactComponent } from './react-component-lib';\n`;
45 /**
46 * Generate JSX import type from correct location.
47 * When using custom elements build, we need to import from
48 * either the "components" directory or customElementsDir
49 * otherwise we risk bundlers pulling in lazy loaded imports.
50 */
51 const generateTypeImports = () => {
52 if (outputTarget.componentCorePackage !== undefined) {
53 const dirPath = outputTarget.includeImportCustomElements
54 ? `/${outputTarget.customElementsDir || 'components'}`
55 : '';
56 return `import type { ${IMPORT_TYPES} } from '${normalizePath(outputTarget.componentCorePackage)}${dirPath}';\n`;
57 }
58 return `import type { ${IMPORT_TYPES} } from '${normalizePath(componentsTypeFile)}';\n`;
59 };
60 const typeImports = generateTypeImports();
61 let sourceImports = '';
62 let registerCustomElements = '';
63 /**
64 * Build an array of Custom Elements build imports and namespace them so that they do not conflict with the React
65 * wrapper names. For example, IonButton would be imported as IonButtonCmp to not conflict with the IonButton React
66 * Component that takes in the Web Component as a parameter.
67 */
68 if (outputTarget.includeImportCustomElements && outputTarget.componentCorePackage !== undefined) {
69 const cmpImports = components.map((component) => {
70 const pascalImport = dashToPascalCase(component.tagName);
71 return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir || 'components'}/${component.tagName}.js';`;
72 });
73 sourceImports = cmpImports.join('\n');
74 }
75 else if (outputTarget.includePolyfills && outputTarget.includeDefineCustomElements) {
76 sourceImports = `import { ${APPLY_POLYFILLS}, ${REGISTER_CUSTOM_ELEMENTS} } from '${pathToCorePackageLoader}';\n`;
77 registerCustomElements = `${APPLY_POLYFILLS}().then(() => ${REGISTER_CUSTOM_ELEMENTS}());`;
78 }
79 else if (!outputTarget.includePolyfills && outputTarget.includeDefineCustomElements) {
80 sourceImports = `import { ${REGISTER_CUSTOM_ELEMENTS} } from '${pathToCorePackageLoader}';\n`;
81 registerCustomElements = `${REGISTER_CUSTOM_ELEMENTS}();`;
82 }
83 const final = [
84 imports,
85 typeImports,
86 sourceImports,
87 registerCustomElements,
88 components
89 .map((cmpMeta) => createComponentDefinition(cmpMeta, outputTarget.includeImportCustomElements))
90 .join('\n'),
91 ];
92 return final.join('\n') + '\n';
93}
94/**
95 * Defines the React component that developers will import to use in their applications.
96 * @param cmpMeta Meta data for a single Web Component
97 * @param includeCustomElement If `true`, the Web Component instance will be passed in to createReactComponent to be
98 * registered with the Custom Elements Registry.
99 * @returns An array where each entry is a string version of the React component definition.
100 */
101export function createComponentDefinition(cmpMeta, includeCustomElement = false) {
102 const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
103 let template = `export const ${tagNameAsPascal} = /*@__PURE__*/createReactComponent<${IMPORT_TYPES}.${tagNameAsPascal}, HTML${tagNameAsPascal}Element>('${cmpMeta.tagName}'`;
104 if (includeCustomElement) {
105 template += `, undefined, undefined, define${tagNameAsPascal}`;
106 }
107 template += `);`;
108 return [template];
109}
110/**
111 * Copy resources used to generate the Stencil-React bindings. The resources copied here are not specific a project's
112 * Stencil components, but rather the logic used to do the actual component generation.
113 * @param config the Stencil configuration associated with the project
114 * @param outputTarget the output target configuration for generating the Stencil-React bindings
115 * @returns The results of performing the copy
116 */
117async function copyResources(config, outputTarget) {
118 if (!config.sys || !config.sys.copy || !config.sys.glob) {
119 throw new Error('stencil is not properly initialized at this step. Notify the developer');
120 }
121 const srcDirectory = path.join(__dirname, '..', 'react-component-lib');
122 const destDirectory = path.join(path.dirname(outputTarget.proxiesFile), 'react-component-lib');
123 return config.sys.copy([
124 {
125 src: srcDirectory,
126 dest: destDirectory,
127 keepDirStructure: false,
128 warn: false,
129 },
130 ], srcDirectory);
131}
132/**
133 * Derive the path to the loader
134 * @param config the Stencil configuration for the project
135 * @param outputTarget the output target used for generating the Stencil-React bindings
136 * @returns the derived loader path
137 */
138export function getPathToCorePackageLoader(config, outputTarget) {
139 var _a;
140 const basePkg = outputTarget.componentCorePackage || '';
141 const distOutputTarget = (_a = config.outputTargets) === null || _a === void 0 ? void 0 : _a.find((o) => o.type === 'dist');
142 const distAbsEsmLoaderPath = (distOutputTarget === null || distOutputTarget === void 0 ? void 0 : distOutputTarget.esmLoaderPath) && path.isAbsolute(distOutputTarget.esmLoaderPath)
143 ? distOutputTarget.esmLoaderPath
144 : null;
145 const distRelEsmLoaderPath = config.rootDir && distAbsEsmLoaderPath ? path.relative(config.rootDir, distAbsEsmLoaderPath) : null;
146 const loaderDir = outputTarget.loaderDir || distRelEsmLoaderPath || DEFAULT_LOADER_DIR;
147 return normalizePath(path.join(basePkg, loaderDir));
148}
149export const GENERATED_DTS = 'components.d.ts';
150const IMPORT_TYPES = 'JSX';
151const REGISTER_CUSTOM_ELEMENTS = 'defineCustomElements';
152const APPLY_POLYFILLS = 'applyPolyfills';
153const DEFAULT_LOADER_DIR = '/dist/loader';