Yuki Takei 4 лет назад
Родитель
Сommit
333396c5f3

+ 6 - 8
packages/app/bin/download-cdn-resources.ts

@@ -3,14 +3,15 @@
  *
  * @author Yuki Takei <yuki@weseek.co.jp>
  */
-require('module-alias/register');
+import { envUtils } from 'growi-commons';
 
-const logger = require('@alias/logger')('growi:bin:download-cdn-resources');
+import CdnResourcesDownloader from '../src/services/cdn-resources-downloader';
+import loggerFactory from '../src/utils/logger';
 
-const { envUtils } = require('growi-commons');
+const logger = loggerFactory('growi:bin:download-cdn-resources');
 
 // check env var
-const noCdn = envUtils.toBoolean(process.env.NO_CDN);
+const noCdn: boolean = envUtils.toBoolean(process.env.NO_CDN);
 if (!noCdn) {
   logger.info('Using CDN. No resources are downloaded.');
   // exit
@@ -19,13 +20,10 @@ if (!noCdn) {
 
 logger.info('This is NO_CDN mode. Start to download resources.');
 
-const CdnResourcesDownloader = require('@commons/service/cdn-resources-downloader');
-const CdnResourcesService = require('@commons/service/cdn-resources-service');
 
 const downloader = new CdnResourcesDownloader();
-const service = new CdnResourcesService();
 
-service.downloadAndWriteAll(downloader)
+downloader.downloadAndWriteAll()
   .then(() => {
     logger.info('Download is completed successfully');
   })

+ 8 - 0
packages/app/config/cdn.js

@@ -0,0 +1,8 @@
+import path from 'path';
+
+import { projectRoot } from '~/utils/project-dir-utils';
+
+export const cdnLocalScriptRoot = path.join(projectRoot, 'public/static/js/cdn');
+export const cdnLocalScriptWebRoot = '/static/js/cdn';
+export const cdnLocalStyleRoot = path.join(projectRoot, 'public/static/styles/cdn');
+export const cdnLocalStyleWebRoot = '/static/styles/cdn';

+ 17 - 0
packages/app/src/interfaces/cdn.ts

@@ -0,0 +1,17 @@
+export type CdnManifestArgs = {
+  integrity?: string,
+  async?: boolean,
+  defer?: boolean,
+};
+
+export type CdnManifest = {
+  name: string,
+  url: string,
+  groups?: string[],
+  args?: CdnManifestArgs,
+};
+
+export type CdnResource = {
+  manifest: CdnManifest,
+  outDir: string,
+};

+ 76 - 69
packages/app/src/services/cdn-resources-downloader.ts

@@ -1,39 +1,60 @@
-const axios = require('axios');
-const path = require('path');
-const { URL } = require('url');
-const urljoin = require('url-join');
-const fs = require('graceful-fs');
-const mkdirp = require('mkdirp');
-const replaceStream = require('replacestream');
-const streamToPromise = require('stream-to-promise');
+import path from 'path';
+import { URL } from 'url';
+import urljoin from 'url-join';
+import { Transform } from 'stream';
+import replaceStream from 'replacestream';
 
-const CdnResource = require('../models/cdn-resource');
+import { cdnLocalScriptRoot, cdnLocalStyleRoot, cdnLocalStyleWebRoot } from '^/config/cdn';
+import * as cdnManifests from '^/resource/cdn-manifests';
 
+import { CdnResource, CdnManifest } from '~/interfaces/cdn';
+import loggerFactory from '~/utils/logger';
+import { downloadTo } from '~/utils/download';
 
-class CdnResourcesDownloader {
+const logger = loggerFactory('growi:service:CdnResourcesDownloader');
 
-  constructor() {
-    this.logger = require('@alias/logger')('growi:service:CdnResourcesDownloader');
+export default class CdnResourcesDownloader {
+
+  async downloadAndWriteAll(): Promise<any> {
+    const cdnScriptResources: CdnResource[] = cdnManifests.js.map((manifest: CdnManifest) => {
+      return { manifest, outDir: cdnLocalScriptRoot };
+    });
+    const cdnStyleResources: CdnResource[] = cdnManifests.style.map((manifest) => {
+      return { manifest, outDir: cdnLocalStyleRoot };
+    });
+
+    const dlStylesOptions = {
+      replaceUrl: {
+        webroot: cdnLocalStyleWebRoot,
+      },
+    };
+
+    return Promise.all([
+      this.downloadScripts(cdnScriptResources),
+      this.downloadStyles(cdnStyleResources, dlStylesOptions),
+    ]);
   }
 
   /**
    * Download script files from CDN
-   * @param {CdnResource[]} cdnResources JavaScript resource data
-   * @param {any} options
+   * @param cdnResources JavaScript resource data
+   * @param options
    */
-  async downloadScripts(cdnResources, options) {
-    this.logger.debug('Downloading scripts', cdnResources);
+  private async downloadScripts(cdnResources: CdnResource[], options?: any): Promise<any> {
+    logger.debug('Downloading scripts', cdnResources);
 
     const opts = Object.assign({}, options);
     const ext = opts.ext || 'js';
 
     const promises = cdnResources.map((cdnResource) => {
-      this.logger.info(`Processing CdnResource '${cdnResource.name}'`);
+      const { manifest } = cdnResource;
 
-      return this.downloadAndWriteToFS(
-        cdnResource.url,
+      logger.info(`Processing CdnResource '${manifest.name}'`);
+
+      return downloadTo(
+        manifest.url,
         cdnResource.outDir,
-        `${cdnResource.name}.${ext}`,
+        `${manifest.name}.${ext}`,
       );
     });
 
@@ -43,31 +64,33 @@ class CdnResourcesDownloader {
   /**
    * Download style sheet file from CDN
    *  Assets in CSS is also downloaded
-   * @param {CdnResource[]} cdnResources CSS resource data
-   * @param {any} options
+   * @param cdnResources CSS resource data
+   * @param options
    */
-  async downloadStyles(cdnResources, options) {
-    this.logger.debug('Downloading styles', cdnResources);
+  private async downloadStyles(cdnResources: CdnResource[], options?: any): Promise<any> {
+    logger.debug('Downloading styles', cdnResources);
 
     const opts = Object.assign({}, options);
     const ext = opts.ext || 'css';
 
     // styles
-    const assetsResourcesStore = [];
+    const assetsResourcesStore: CdnResource[] = [];
     const promisesForStyle = cdnResources.map((cdnResource) => {
-      this.logger.info(`Processing CdnResource '${cdnResource.name}'`);
+      const { manifest } = cdnResource;
+
+      logger.info(`Processing CdnResource '${manifest.name}'`);
 
-      let urlReplacer = null;
+      let urlReplacer: Transform|null = null;
 
       // generate replaceStream instance
       if (opts.replaceUrl != null) {
         urlReplacer = this.generateReplaceUrlInCssStream(cdnResource, assetsResourcesStore, opts.replaceUrl.webroot);
       }
 
-      return this.downloadAndWriteToFS(
-        cdnResource.url,
+      return downloadTo(
+        manifest.url,
         cdnResource.outDir,
-        `${cdnResource.name}.${ext}`,
+        `${manifest.name}.${ext}`,
         urlReplacer,
       );
     });
@@ -75,16 +98,18 @@ class CdnResourcesDownloader {
     // wait until all styles are downloaded
     await Promise.all(promisesForStyle);
 
-    this.logger.debug('Downloading assets', assetsResourcesStore);
+    logger.debug('Downloading assets', assetsResourcesStore);
 
     // assets in css
     const promisesForAssets = assetsResourcesStore.map((cdnResource) => {
-      this.logger.info(`Processing assts in css '${cdnResource.name}'`);
+      const { manifest } = cdnResource;
+
+      logger.info(`Processing assts in css '${manifest.name}'`);
 
-      return this.downloadAndWriteToFS(
-        cdnResource.url,
+      return downloadTo(
+        manifest.url,
         cdnResource.outDir,
-        cdnResource.name,
+        manifest.name,
       );
     });
 
@@ -98,54 +123,36 @@ class CdnResourcesDownloader {
    *  Before  : url(../images/logo.svg)
    *  After   : url(/path/to/webroot/${cdnResources.name}/logo.svg)
    *
-   * @param {CdnResource[]} cdnResource CSS resource data
-   * @param {CdnResource[]} assetsResourcesStore An array to store CdnResource that is detected by 'url()' in CSS
-   * @param {string} webroot
+   * @param cdnResource CSS resource data
+   * @param assetsResourcesStore An array to store CdnResource that is detected by 'url()' in CSS
+   * @param webroot
    */
-  generateReplaceUrlInCssStream(cdnResource, assetsResourcesStore, webroot) {
+  private generateReplaceUrlInCssStream(cdnResource: CdnResource, assetsResourcesStore: CdnResource[], webroot: string): Transform {
     return replaceStream(
       /url\((?!['"]?data:)["']?(.+?)["']?\)/g, // https://regex101.com/r/Sds38A/3
       (match, url) => {
         // generate URL Object
         const parsedUrl = url.startsWith('http')
           ? new URL(url) // when url is fqcn
-          : new URL(url, cdnResource.url); // when url is relative
+          : new URL(url, cdnResource.manifest.url); // when url is relative
         const basename = path.basename(parsedUrl.pathname);
 
-        this.logger.debug(`${cdnResource.name} has ${parsedUrl.toString()}`);
+        logger.debug(`${cdnResource.manifest.name} has ${parsedUrl.toString()}`);
 
         // add assets metadata to download later
-        assetsResourcesStore.push(
-          new CdnResource(
-            basename,
-            parsedUrl.toString(),
-            path.join(cdnResource.outDir, cdnResource.name),
-          ),
-        );
-
-        const replaceUrl = urljoin(webroot, cdnResource.name, basename);
+        const replacedCdnResource = {
+          manifest: {
+            name: basename,
+            url: parsedUrl.toString(),
+          },
+          outDir: path.join(cdnResource.outDir, cdnResource.manifest.name),
+        };
+        assetsResourcesStore.push(replacedCdnResource);
+
+        const replaceUrl = urljoin(webroot, cdnResource.manifest.name, basename);
         return `url(${replaceUrl})`;
       },
     );
   }
 
-  async downloadAndWriteToFS(url, outDir, fileName, replacestream) {
-    // get
-    const response = await axios.get(url, { responseType: 'stream' });
-    // mkdir -p
-    mkdirp.sync(outDir);
-
-    // replace and write
-    let stream = response.data;
-    if (replacestream != null) {
-      stream = stream.pipe(replacestream);
-    }
-    const file = path.join(outDir, fileName);
-    stream = stream.pipe(fs.createWriteStream(file));
-
-    return streamToPromise(stream);
-  }
-
 }
-
-module.exports = CdnResourcesDownloader;