UNPKG

8.28 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 ? `/${outputTarget.customElementsDir || 'components'}` : '';
54 return `import type { ${IMPORT_TYPES} } from '${normalizePath(outputTarget.componentCorePackage)}${dirPath}';\n`;
55 }
56 return `import type { ${IMPORT_TYPES} } from '${normalizePath(componentsTypeFile)}';\n`;
57 };
58 const typeImports = generateTypeImports();
59 let sourceImports = '';
60 let registerCustomElements = '';
61 /**
62 * Build an array of Custom Elements build imports and namespace them so that they do not conflict with the React
63 * wrapper names. For example, IonButton would be imported as IonButtonCmp to not conflict with the IonButton React
64 * Component that takes in the Web Component as a parameter.
65 */
66 if (outputTarget.includeImportCustomElements && outputTarget.componentCorePackage !== undefined) {
67 const cmpImports = components.map(component => {
68 const pascalImport = dashToPascalCase(component.tagName);
69 return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir ||
70 'components'}/${component.tagName}.js';`;
71 });
72 sourceImports = cmpImports.join('\n');
73 }
74 else if (outputTarget.includePolyfills && outputTarget.includeDefineCustomElements) {
75 sourceImports = `import { ${APPLY_POLYFILLS}, ${REGISTER_CUSTOM_ELEMENTS} } from '${pathToCorePackageLoader}';\n`;
76 registerCustomElements = `${APPLY_POLYFILLS}().then(() => ${REGISTER_CUSTOM_ELEMENTS}());`;
77 }
78 else if (!outputTarget.includePolyfills && outputTarget.includeDefineCustomElements) {
79 sourceImports = `import { ${REGISTER_CUSTOM_ELEMENTS} } from '${pathToCorePackageLoader}';\n`;
80 registerCustomElements = `${REGISTER_CUSTOM_ELEMENTS}();`;
81 }
82 const final = [
83 imports,
84 typeImports,
85 sourceImports,
86 registerCustomElements,
87 components.map(cmpMeta => createComponentDefinition(cmpMeta, outputTarget.includeImportCustomElements)).join('\n'),
88 ];
89 return final.join('\n') + '\n';
90}
91/**
92 * Defines the React component that developers will import to use in their applications.
93 * @param cmpMeta Meta data for a single Web Component
94 * @param includeCustomElement If `true`, the Web Component instance will be passed in to createReactComponent to be
95 * registered with the Custom Elements Registry.
96 * @returns An array where each entry is a string version of the React component definition.
97 */
98export function createComponentDefinition(cmpMeta, includeCustomElement = false) {
99 const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
100 let template = `export const ${tagNameAsPascal} = /*@__PURE__*/createReactComponent<${IMPORT_TYPES}.${tagNameAsPascal}, HTML${tagNameAsPascal}Element>('${cmpMeta.tagName}'`;
101 if (includeCustomElement) {
102 template += `, undefined, undefined, define${tagNameAsPascal}`;
103 }
104 template += `);`;
105 return [
106 template
107 ];
108}
109/**
110 * Copy resources used to generate the Stencil-React bindings. The resources copied here are not specific a project's
111 * Stencil components, but rather the logic used to do the actual component generation.
112 * @param config the Stencil configuration associated with the project
113 * @param outputTarget the output target configuration for generating the Stencil-React bindings
114 * @returns The results of performing the copy
115 */
116async function copyResources(config, outputTarget) {
117 if (!config.sys || !config.sys.copy || !config.sys.glob) {
118 throw new Error('stencil is not properly initialized at this step. Notify the developer');
119 }
120 const srcDirectory = path.join(__dirname, '..', 'react-component-lib');
121 const destDirectory = path.join(path.dirname(outputTarget.proxiesFile), 'react-component-lib');
122 return config.sys.copy([
123 {
124 src: srcDirectory,
125 dest: destDirectory,
126 keepDirStructure: false,
127 warn: false,
128 },
129 ], srcDirectory);
130}
131/**
132 * Derive the path to the loader
133 * @param config the Stencil configuration for the project
134 * @param outputTarget the output target used for generating the Stencil-React bindings
135 * @returns the derived loader path
136 */
137export function getPathToCorePackageLoader(config, outputTarget) {
138 var _a;
139 const basePkg = outputTarget.componentCorePackage || '';
140 const distOutputTarget = (_a = config.outputTargets) === null || _a === void 0 ? void 0 : _a.find((o) => o.type === 'dist');
141 const distAbsEsmLoaderPath = (distOutputTarget === null || distOutputTarget === void 0 ? void 0 : distOutputTarget.esmLoaderPath) && path.isAbsolute(distOutputTarget.esmLoaderPath)
142 ? distOutputTarget.esmLoaderPath
143 : null;
144 const distRelEsmLoaderPath = config.rootDir && distAbsEsmLoaderPath
145 ? path.relative(config.rootDir, distAbsEsmLoaderPath)
146 : null;
147 const loaderDir = outputTarget.loaderDir || distRelEsmLoaderPath || DEFAULT_LOADER_DIR;
148 return normalizePath(path.join(basePkg, loaderDir));
149}
150export const GENERATED_DTS = 'components.d.ts';
151const IMPORT_TYPES = 'JSX';
152const REGISTER_CUSTOM_ELEMENTS = 'defineCustomElements';
153const APPLY_POLYFILLS = 'applyPolyfills';
154const DEFAULT_LOADER_DIR = '/dist/loader';