| |
| |
| |
| export interface ComponentFile { |
| path: string; |
| moduleName: string; |
| componentName: string; |
| } |
|
|
| export interface PackageDefinition { |
| name: string; |
| components: { |
| moduleName: string; |
| componentName: string; |
| }[]; |
| } |
|
|
| const isLazyLoadingModule = (componentPath: string) => |
| componentPath.includes(".dynamic"); |
|
|
| const removeDynamicModuleNameEnding = (moduleName: string) => |
| moduleName.replace(/\.?dynamic$/i, ""); |
|
|
| |
| |
| |
| |
| |
| function generateComponentFactory( |
| components: (PackageDefinition | ComponentFile)[], |
| ): string { |
| const componentFiles = components.filter( |
| (component) => (component as ComponentFile).path, |
| ) as ComponentFile[]; |
| const packages = components.filter( |
| (component) => (component as PackageDefinition).components, |
| ) as PackageDefinition[]; |
|
|
| const hasLazyModules = componentFiles.find((component) => |
| isLazyLoadingModule(component.path), |
| ); |
|
|
| return `/* eslint-disable */ |
| // Do not edit this file, it is auto-generated at build time! |
| // See scripts/generate-component-factory.ts to modify the generation of this file. |
| |
| ${hasLazyModules ? "import dynamic from 'next/dynamic'" : ""} |
| |
| ${packages.map((pkg) => { |
| const list = pkg.components.map((c) => c.moduleName).join(", "); |
| |
| return `import { ${list} } from '${pkg.name}'`; |
| })} |
| ${componentFiles |
| .map((component) => { |
| if (isLazyLoadingModule(component.path)) { |
| const moduleName = removeDynamicModuleNameEnding(component.moduleName); |
| return `const ${moduleName} = { |
| module: () => import('${component.path}'), |
| element: (isEditing?: boolean) => isEditing ? require('${component.path}')?.default : dynamic(${moduleName}.module) |
| }`; |
| } |
| |
| return `import * as ${component.moduleName} from '${component.path}';`; |
| }) |
| .join("\n")} |
| |
| const components = new Map(); |
| ${packages.map((p) => |
| p.components.map( |
| (component) => |
| `components.set('${component.componentName}', ${component.moduleName})`, |
| ), |
| )} |
| ${componentFiles |
| .map( |
| (component) => |
| `components.set('${ |
| isLazyLoadingModule(component.path) |
| ? removeDynamicModuleNameEnding(component.componentName) |
| : component.componentName |
| }', ${ |
| isLazyLoadingModule(component.path) |
| ? removeDynamicModuleNameEnding(component.moduleName) |
| : component.moduleName |
| });`, |
| ) |
| .join("\n")} |
| |
| // Next.js 'dynamic' import and JavaScript 'dynamic' import are different. |
| // Next.js 'dynamic(...)' returns common 'React.ComponentType' while |
| // 'import('...')' returns 'Promise' that will resolve module. |
| // componentModule uses 'import(...)' because primary usage of it to get not only 'React Component' (default export) but all named exports. |
| // See https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports |
| // componentFactory uses 'dynamic(...)' because primary usage of it to render 'React Component' (default export). |
| // See https://nextjs.org/docs/advanced-features/dynamic-import |
| // At the end you will have single preloaded script for each lazy loading module. |
| // Editing mode doesn't work well with dynamic components in nextjs: dynamic components are not displayed without refresh after a rendering is added. |
| // This happens because Sitecore editors simply insert updated HTML generated on server side. This conflicts with nextjs dynamic logic as no HTML gets rendered for dynamic component |
| // So we use require() to obtain dynamic components in editing mode while preserving dynamic logic for non-editing scenarios |
| // As we need to be able to seamlessly work with dynamic components in both editing and normal modes, different componentFactory functions will be passed to app |
| |
| export function componentModule(componentName: string) { |
| const component = components.get(componentName); |
| |
| // check that component is lazy loading module |
| if (!component?.default && component?.module) { |
| // return js dynamic import |
| return component.module(); |
| } |
| |
| return component; |
| } |
| |
| function baseComponentFactory(componentName: string, exportName?: string, isEditing?: boolean) { |
| const DEFAULT_EXPORT_NAME = 'Default'; |
| const component = components.get(componentName); |
| |
| // check that component should be dynamically imported |
| if (component?.element) { |
| // return next.js dynamic import |
| return component.element(isEditing); |
| } |
| |
| if (exportName && exportName !== DEFAULT_EXPORT_NAME) { |
| return component[exportName]; |
| } |
| |
| return component?.Default || component?.default || component; |
| } |
| |
| export function componentFactory(componentName: string, exportName?: string) { |
| return baseComponentFactory(componentName, exportName, false); |
| } |
| |
| export function editingComponentFactory(componentName: string, exportName?: string) { |
| return baseComponentFactory(componentName, exportName, true); |
| } |
| `; |
| } |
|
|
| export default generateComponentFactory; |
|
|