import loggerFactory from '~/utils/logger'; import { resolveFromRoot } from '~/utils/project-dir-utils'; const { URL } = require('url'); const urljoin = require('url-join'); const { envUtils } = require('@growi/core'); const cdnLocalScriptRoot = 'public/static/js/cdn'; const cdnLocalScriptWebRoot = '/static/js/cdn'; const cdnLocalStyleRoot = 'public/static/styles/cdn'; const cdnLocalStyleWebRoot = '/static/styles/cdn'; class CdnResourcesService { constructor() { this.logger = loggerFactory('growi:service:CdnResourcesService'); this.loadManifests(); } get noCdn() { return envUtils.toBoolean(process.env.NO_CDN); } loadManifests() { this.cdnManifests = require('^/resource/cdn-manifests'); this.logger.debug('manifest data loaded : ', this.cdnManifests); } getScriptManifestByName(name) { const manifests = this.cdnManifests.js .filter((manifest) => { return manifest.name === name }); return (manifests.length > 0) ? manifests[0] : null; } getStyleManifestByName(name) { const manifests = this.cdnManifests.style .filter((manifest) => { return manifest.name === name }); return (manifests.length > 0) ? manifests[0] : null; } /** * download all resources from CDN and write to FS * * !! This method should be invoked only by /bin/download-cdn-resources when build client !! * * @param {CdnResourceDownloader} cdnResourceDownloader */ async downloadAndWriteAll(cdnResourceDownloader) { const CdnResource = require('~/models/cdn-resource'); const cdnScriptResources = this.cdnManifests.js.map((manifest) => { const outDir = resolveFromRoot(cdnLocalScriptRoot); return new CdnResource(manifest.name, manifest.url, outDir); }); const cdnStyleResources = this.cdnManifests.style.map((manifest) => { const outDir = resolveFromRoot(cdnLocalStyleRoot); return new CdnResource(manifest.name, manifest.url, outDir); }); const dlStylesOptions = { replaceUrl: { webroot: cdnLocalStyleWebRoot, }, }; return Promise.all([ cdnResourceDownloader.downloadScripts(cdnScriptResources), cdnResourceDownloader.downloadStyles(cdnStyleResources, dlStylesOptions), ]); } /** * Generate script tag string * * @param {Object} manifest */ generateScriptTag(manifest) { const attrs = []; const args = manifest.args || {}; if (args.async) { attrs.push('async'); } if (args.defer) { attrs.push('defer'); } // TODO process integrity const url = this.noCdn ? `${urljoin(cdnLocalScriptWebRoot, manifest.name)}.js` : manifest.url; return ``; } getScriptTagByName(name) { const manifest = this.getScriptManifestByName(name); return this.generateScriptTag(manifest); } getScriptTagsByGroup(group) { return this.cdnManifests.js .filter((manifest) => { return manifest.groups != null && manifest.groups.includes(group); }) .map((manifest) => { return this.generateScriptTag(manifest); }); } /** * Generate style tag string * * @param {Object} manifest */ generateStyleTag(manifest) { const attrs = []; const args = manifest.args || {}; if (args.async) { attrs.push('async'); } if (args.defer) { attrs.push('defer'); } // TODO process integrity const url = this.noCdn ? `${urljoin(cdnLocalStyleWebRoot, manifest.name)}.css` : manifest.url; return ``; } getStyleTagByName(name) { const manifest = this.getStyleManifestByName(name); return this.generateStyleTag(manifest); } getStyleTagsByGroup(group) { return this.cdnManifests.style .filter((manifest) => { return manifest.groups != null && manifest.groups.includes(group); }) .map((manifest) => { return this.generateStyleTag(manifest); }); } getHighlightJsStyleTag(styleName) { let manifest = this.getStyleManifestByName('highlight-theme-github'); // replace style if (!this.noCdn) { const url = new URL(`${styleName}.css`, manifest.url); // resolve `${styleName}.css` from manifest.url // clone manifest manifest = Object.assign(manifest, { url: url.toString() }); } return this.generateStyleTag(manifest); } } module.exports = CdnResourcesService;