ソースを参照

feat(ui-devkit): Allow custom global styles to be specified

Relates to #391
Michael Bromley 5 年 前
コミット
2081a157fc

+ 1 - 1
packages/ui-devkit/scaffold/angular.json

@@ -42,7 +42,7 @@
               "src/i18n-messages"
             ],
             "styles": [
-              "src/styles/styles.scss"
+              "src/global-styles.scss"
             ],
             "stylePreprocessorOptions": {
               "includePaths": [

+ 1 - 0
packages/ui-devkit/scaffold/src/global-styles.scss

@@ -0,0 +1 @@
+@import "./styles/styles";

+ 29 - 2
packages/ui-devkit/src/compiler/compile.ts

@@ -7,7 +7,7 @@ import * as fs from 'fs-extra';
 import * as path from 'path';
 
 import { DEFAULT_BASE_HREF, MODULES_OUTPUT_DIR } from './constants';
-import { setupScaffold } from './scaffold';
+import { copyGlobalStyleFile, setupScaffold } from './scaffold';
 import { getAllTranslationFiles, mergeExtensionTranslations } from './translations';
 import { Extension, StaticAssetDefinition, UiExtensionCompilerOptions } from './types';
 import {
@@ -15,6 +15,7 @@ import {
     copyUiDevkit,
     getStaticAssetPath,
     isAdminUiExtension,
+    isGlobalStylesExtension,
     isStaticAssetExtension,
     isTranslationExtension,
     normalizeExtensions,
@@ -89,6 +90,7 @@ function runWatchMode(
             await setupScaffold(outputPath, extensions);
             const adminUiExtensions = extensions.filter(isAdminUiExtension);
             const normalizedExtensions = normalizeExtensions(adminUiExtensions);
+            const globalStylesExtensions = extensions.filter(isGlobalStylesExtension);
             const staticAssetExtensions = extensions.filter(isStaticAssetExtension);
             const allTranslationFiles = getAllTranslationFiles(extensions.filter(isTranslationExtension));
             buildProcess = spawn(cmd, ['run', 'start', `--port=${port}`, `--base-href=${baseHref}`], {
@@ -126,6 +128,18 @@ function runWatchMode(
                     }
                 }
             }
+            for (const extension of globalStylesExtensions) {
+                const globalStylePaths = Array.isArray(extension.globalStyles)
+                    ? extension.globalStyles
+                    : [extension.globalStyles];
+                for (const stylePath of globalStylePaths) {
+                    if (!watcher) {
+                        watcher = chokidarWatch(stylePath);
+                    } else {
+                        watcher.add(stylePath);
+                    }
+                }
+            }
             for (const translationFiles of Object.values(allTranslationFiles)) {
                 if (!translationFiles) {
                     continue;
@@ -145,10 +159,17 @@ function runWatchMode(
             }
 
             if (watcher) {
-                const allStaticAssetDefs = adminUiExtensions.reduce(
+                const allStaticAssetDefs = staticAssetExtensions.reduce(
                     (defs, e) => [...defs, ...(e.staticAssets || [])],
                     [] as StaticAssetDefinition[],
                 );
+                const allGlobalStyles = globalStylesExtensions.reduce(
+                    (defs, e) => [
+                        ...defs,
+                        ...(Array.isArray(e.globalStyles) ? e.globalStyles : [e.globalStyles]),
+                    ],
+                    [] as string[],
+                );
 
                 watcher.on('change', async filePath => {
                     const extension = normalizedExtensions.find(e => filePath.includes(e.extensionPath));
@@ -168,6 +189,12 @@ function runWatchMode(
                             return;
                         }
                     }
+                    for (const stylePath of allGlobalStyles) {
+                        if (filePath.includes(stylePath)) {
+                            await copyGlobalStyleFile(outputPath, stylePath);
+                            return;
+                        }
+                    }
                     for (const languageCode of Object.keys(allTranslationFiles)) {
                         // tslint:disable-next-line:no-non-null-assertion
                         const translationFiles = allTranslationFiles[languageCode as LanguageCode]!;

+ 1 - 0
packages/ui-devkit/src/compiler/constants.ts

@@ -1,4 +1,5 @@
 export const STATIC_ASSETS_OUTPUT_DIR = 'static-assets';
+export const GLOBAL_STYLES_OUTPUT_DIR = 'global-styles';
 export const EXTENSION_ROUTES_FILE = 'src/extension.routes.ts';
 export const SHARED_EXTENSIONS_FILE = 'src/shared-extensions.module.ts';
 export const MODULES_OUTPUT_DIR = 'src/extensions';

+ 43 - 3
packages/ui-devkit/src/compiler/scaffold.ts

@@ -3,19 +3,27 @@ import { spawn } from 'child_process';
 import * as fs from 'fs-extra';
 import * as path from 'path';
 
-import { EXTENSION_ROUTES_FILE, MODULES_OUTPUT_DIR, SHARED_EXTENSIONS_FILE } from './constants';
+import {
+    EXTENSION_ROUTES_FILE,
+    GLOBAL_STYLES_OUTPUT_DIR,
+    MODULES_OUTPUT_DIR,
+    SHARED_EXTENSIONS_FILE,
+    STATIC_ASSETS_OUTPUT_DIR,
+} from './constants';
 import { getAllTranslationFiles, mergeExtensionTranslations } from './translations';
 import {
     AdminUiExtension,
     AdminUiExtensionLazyModule,
     AdminUiExtensionSharedModule,
     Extension,
+    GlobalStylesExtension,
     StaticAssetExtension,
 } from './types';
 import {
     copyStaticAsset,
     copyUiDevkit,
     isAdminUiExtension,
+    isGlobalStylesExtension,
     isStaticAssetExtension,
     isTranslationExtension,
     logger,
@@ -25,7 +33,7 @@ import {
 
 export async function setupScaffold(outputPath: string, extensions: Extension[]) {
     deleteExistingExtensionModules(outputPath);
-    copySourceIfNotExists(outputPath);
+    copyAdminUiSource(outputPath);
 
     const adminUiExtensions = extensions.filter(isAdminUiExtension);
     const normalizedExtensions = normalizeExtensions(adminUiExtensions);
@@ -34,6 +42,9 @@ export async function setupScaffold(outputPath: string, extensions: Extension[])
     const staticAssetExtensions = extensions.filter(isStaticAssetExtension);
     await copyStaticAssets(outputPath, staticAssetExtensions);
 
+    const globalStyleExtensions = extensions.filter(isGlobalStylesExtension);
+    await addGlobalStyles(outputPath, globalStyleExtensions);
+
     const allTranslationFiles = getAllTranslationFiles(extensions.filter(isTranslationExtension));
     await mergeExtensionTranslations(outputPath, allTranslationFiles);
 
@@ -82,6 +93,35 @@ async function copyStaticAssets(outputPath: string, extensions: Array<Partial<St
     }
 }
 
+async function addGlobalStyles(outputPath: string, extensions: GlobalStylesExtension[]) {
+    const globalStylesDir = path.join(outputPath, 'src', GLOBAL_STYLES_OUTPUT_DIR);
+    await fs.remove(globalStylesDir);
+    await fs.ensureDir(globalStylesDir);
+    const imports: string[] = [];
+    for (const extension of extensions) {
+        const styleFiles = Array.isArray(extension.globalStyles)
+            ? extension.globalStyles
+            : [extension.globalStyles];
+        for (const styleFile of styleFiles) {
+            await copyGlobalStyleFile(outputPath, styleFile);
+            imports.push(path.basename(styleFile, path.extname(styleFile)));
+        }
+    }
+    const globalStylesSource =
+        `@import "./styles/styles";\n` +
+        imports.map(file => `@import "./${GLOBAL_STYLES_OUTPUT_DIR}/${file}";`).join('\n');
+
+    const globalStylesFile = path.join(outputPath, 'src', 'global-styles.scss');
+    await fs.writeFile(globalStylesFile, globalStylesSource, 'utf-8');
+}
+
+export async function copyGlobalStyleFile(outputPath: string, stylePath: string) {
+    const globalStylesDir = path.join(outputPath, 'src', GLOBAL_STYLES_OUTPUT_DIR);
+    const fileBasename = path.basename(stylePath);
+    const styleOutputPath = path.join(globalStylesDir, fileBasename);
+    await fs.copyFile(stylePath, styleOutputPath);
+}
+
 function generateLazyExtensionRoutes(extensions: Array<Required<AdminUiExtension>>): string {
     const routes: string[] = [];
     for (const extension of extensions as Array<Required<AdminUiExtension>>) {
@@ -136,7 +176,7 @@ function getModuleFilePath(
  * Copy the Admin UI sources & static assets to the outputPath if it does not already
  * exists there.
  */
-function copySourceIfNotExists(outputPath: string) {
+function copyAdminUiSource(outputPath: string) {
     const angularJsonFile = path.join(outputPath, 'angular.json');
     const indexFile = path.join(outputPath, '/src/index.html');
     if (fs.existsSync(angularJsonFile) && fs.existsSync(indexFile)) {

+ 29 - 3
packages/ui-devkit/src/compiler/types.ts

@@ -1,6 +1,10 @@
 import { LanguageCode } from '@vendure/common/lib/generated-types';
 
-export type Extension = AdminUiExtension | TranslationExtension | StaticAssetExtension;
+export type Extension =
+    | AdminUiExtension
+    | TranslationExtension
+    | StaticAssetExtension
+    | GlobalStylesExtension;
 
 /**
  * @description
@@ -32,6 +36,9 @@ export interface TranslationExtension {
 /**
  * @description
  * Defines extensions which copy static assets to the custom Admin UI application source asset directory.
+ *
+ * @docsCategory UiDevkit
+ * @docsPage AdminUiExtension
  */
 export interface StaticAssetExtension {
     /**
@@ -42,6 +49,22 @@ export interface StaticAssetExtension {
     staticAssets: StaticAssetDefinition[];
 }
 
+/**
+ * @description
+ * Defines extensions which add global styles to the custom Admin UI application.
+ *
+ * @docsCategory UiDevkit
+ * @docsPage AdminUiExtension
+ */
+export interface GlobalStylesExtension {
+    /**
+     * @description
+     * Specifies a path (or array of paths) to global style files (css or Sass) which will be
+     * incorporated into the Admin UI app global stylesheet.
+     */
+    globalStyles: string[] | string;
+}
+
 /**
  * @description
  * Defines extensions to the Admin UI application by specifying additional
@@ -55,7 +78,10 @@ export interface StaticAssetExtension {
  * @docsPage AdminUiExtension
  * @docsWeight 0
  */
-export interface AdminUiExtension extends Partial<TranslationExtension>, Partial<StaticAssetExtension> {
+export interface AdminUiExtension
+    extends Partial<TranslationExtension>,
+        Partial<StaticAssetExtension>,
+        Partial<GlobalStylesExtension> {
     /**
      * @description
      * An optional ID for the extension module. Only used internally for generating
@@ -164,7 +190,7 @@ export interface UiExtensionCompilerOptions {
      * An array of objects which configure Angular modules and/or
      * translations with which to extend the Admin UI.
      */
-    extensions: Array<AdminUiExtension | TranslationExtension | StaticAssetExtension>;
+    extensions: Extension[];
     /**
      * @description
      * Set to `true` in order to compile the Admin UI in development mode (using the Angular CLI

+ 6 - 1
packages/ui-devkit/src/compiler/utils.ts

@@ -9,6 +9,7 @@ import { STATIC_ASSETS_OUTPUT_DIR } from './constants';
 import {
     AdminUiExtension,
     Extension,
+    GlobalStylesExtension,
     StaticAssetDefinition,
     StaticAssetExtension,
     TranslationExtension,
@@ -86,7 +87,7 @@ export function normalizeExtensions(extensions?: AdminUiExtension[]): Array<Requ
             id = hash.digest('hex');
         }
 
-        return { staticAssets: [], translations: {}, ...e, id };
+        return { staticAssets: [], translations: {}, globalStyles: [], ...e, id };
     });
 }
 
@@ -101,3 +102,7 @@ export function isTranslationExtension(input: Extension): input is TranslationEx
 export function isStaticAssetExtension(input: Extension): input is StaticAssetExtension {
     return input.hasOwnProperty('staticAssets');
 }
+
+export function isGlobalStylesExtension(input: Extension): input is GlobalStylesExtension {
+    return input.hasOwnProperty('globalStyles');
+}