diff --git a/packages/admin-ui-plugin/src/ui-app-compiler.service.ts b/packages/admin-ui-plugin/src/ui-app-compiler.service.ts index 990291c694..dcadf07f93 100644 --- a/packages/admin-ui-plugin/src/ui-app-compiler.service.ts +++ b/packages/admin-ui-plugin/src/ui-app-compiler.service.ts @@ -76,7 +76,7 @@ export class UiAppCompiler { private getExtensionModulesHash(extensions: Array>): string { let modifiedDates: string[] = []; for (const extension of extensions) { - modifiedDates = [...modifiedDates, ...this.getAllModifiedDates(extension.ngModulePath)]; + modifiedDates = [...modifiedDates, ...this.getAllModifiedDates(extension.extensionPath)]; } const hash = crypto.createHash('sha256'); hash.update(modifiedDates.join('') + JSON.stringify(extensions)); diff --git a/packages/admin-ui/src/devkit/common.ts b/packages/admin-ui/src/devkit/common.ts index c5c4b9ed15..38e7459097 100644 --- a/packages/admin-ui/src/devkit/common.ts +++ b/packages/admin-ui/src/devkit/common.ts @@ -1,4 +1,4 @@ -import { AdminUiExtension } from '@vendure/common/lib/shared-types'; +import { AdminUiExtension, AdminUiExtensionModule } from '@vendure/common/lib/shared-types'; import * as fs from 'fs-extra'; import * as path from 'path'; @@ -23,14 +23,14 @@ export function deleteExistingExtensionModules() { } /** - * Copies all files from the ngModulePaths of the configured extensions into the + * Copies all files from the extensionPaths of the configured extensions into the * admin-ui source tree. */ export function copyExtensionModules(extensions: Array>) { for (const extension of extensions) { - const dirName = path.basename(path.dirname(extension.ngModulePath)); + const dirName = path.basename(path.dirname(extension.extensionPath)); const dest = getModuleOutputDir(extension); - fs.copySync(extension.ngModulePath, dest); + fs.copySync(extension.extensionPath, dest); } } @@ -40,16 +40,21 @@ export function getModuleOutputDir(extension: Required): strin export function createExtensionsModules(extensions: Array>) { const removeTsExtension = (filename: string): string => filename.replace(/\.ts$/, ''); - const importPath = (e: Required): string => - `./${EXTENSIONS_MODULES_DIR}/${e.id}/${removeTsExtension(e.ngModuleFileName)}`; + const importPath = (id: string, fileName: string): string => + `./${EXTENSIONS_MODULES_DIR}/${id}/${removeTsExtension(fileName)}`; for (const type of ['lazy', 'shared'] as const) { - const source = generateExtensionModuleTsSource( - type, - extensions - .filter(e => e.type === type) - .map(e => ({ className: e.ngModuleName, path: importPath(e) })), - ); + const modulesOfType = extensions + .reduce( + (modules, e) => [...modules, ...e.ngModules.map(m => ({ id: e.id, module: m }))], + [] as Array<{ id: string; module: AdminUiExtensionModule }>, + ) + .filter(m => m.module.type === type) + .map(e => ({ + className: e.module.ngModuleName, + path: importPath(e.id, e.module.ngModuleFileName), + })); + const source = generateExtensionModuleTsSource(type, modulesOfType); const filePath = type === 'lazy' ? lazyExtensionsModuleFile : sharedExtensionsModuleFile; fs.writeFileSync(filePath, source, 'utf-8'); } diff --git a/packages/admin-ui/src/devkit/watch.ts b/packages/admin-ui/src/devkit/watch.ts index aa798103de..4a10f20758 100644 --- a/packages/admin-ui/src/devkit/watch.ts +++ b/packages/admin-ui/src/devkit/watch.ts @@ -40,21 +40,21 @@ export function watchAdminUiApp(extensions: Array>, p let watcher: FSWatcher | undefined; for (const extension of extensions) { if (!watcher) { - watcher = watch(extension.ngModulePath, { + watcher = watch(extension.extensionPath, { depth: 4, ignored: '**/node_modules/', }); } else { - watcher.add(extension.ngModulePath); + watcher.add(extension.extensionPath); } } if (watcher) { watcher.on('change', filePath => { - const extension = extensions.find(e => filePath.includes(e.ngModulePath)); + const extension = extensions.find(e => filePath.includes(e.extensionPath)); if (extension) { const outputDir = getModuleOutputDir(extension); - const filePart = path.relative(extension.ngModulePath, filePath); + const filePart = path.relative(extension.extensionPath, filePath); const dest = path.join(outputDir, filePart); fs.copyFile(filePath, dest); } diff --git a/packages/common/src/shared-types.ts b/packages/common/src/shared-types.ts index 26deb8b7f1..3cd287949c 100644 --- a/packages/common/src/shared-types.ts +++ b/packages/common/src/shared-types.ts @@ -5,11 +5,11 @@ * Source: https://stackoverflow.com/a/49936686/772859 */ export type DeepPartial = { - [P in keyof T]?: null | (T[P] extends Array + [P in keyof T]?: null | (T[P] extends Array ? Array> : T[P] extends ReadonlyArray - ? ReadonlyArray> - : DeepPartial) + ? ReadonlyArray> + : DeepPartial) }; // tslint:enable:no-shadowed-variable @@ -22,7 +22,7 @@ export type DeepRequired = T extend ? { [P in keyof T]-?: NonNullable extends NonNullable> ? NonNullable - : DeepRequired, U>; + : DeepRequired, U> } : T; // tslint:enable:ban-types @@ -105,9 +105,31 @@ export interface AdminUiExtension { /** * @description * An optional ID for the extension module. Only used internally for generating - * import paths to your module. + * import paths to your module. If not specified, a unique hash will be used as the id. */ id?: string; + + /** + * @description + * The path to the directory containing the extension module(s). The entire contents of this directory + * will be copied into the Admin UI app, including all TypeScript source files, html templates, + * scss style sheets etc. + */ + extensionPath: string; + /** + * @description + * One or more Angular modules which extend the default Admin UI. + */ + ngModules: AdminUiExtensionModule[]; +} + +/** + * @description + * Configuration defining a single NgModule with which to extend the Admin UI. + * + * @docsCategory AdminUiPlugin + */ +export interface AdminUiExtensionModule { /** * @description * Lazy modules are lazy-loaded at the `/extensions/` route and should be used for @@ -118,13 +140,6 @@ export interface AdminUiExtension { * navigation items. */ type: 'shared' | 'lazy'; - /** - * @description - * The path to the directory containing the extension module. Each extension module - * should be located in its own directory. The entire contents of this directory - * will be copied into the Admin UI app. - */ - ngModulePath: string; /** * @description * The name of the file containing the extension module class.