|
|
@@ -1,111 +1,62 @@
|
|
|
-const axios = require('axios');
|
|
|
-const path = require('path');
|
|
|
-const { URL } = require('url');
|
|
|
const urljoin = require('url-join');
|
|
|
-const fs = require('graceful-fs');
|
|
|
-const replaceStream = require('replacestream');
|
|
|
|
|
|
const helpers = require('@commons/util/helpers');
|
|
|
+
|
|
|
+const CdnResourcesDownloader = require('./cdn-resources-downloader');
|
|
|
+const CdnResource = CdnResourcesDownloader.CdnResource;
|
|
|
+
|
|
|
const cdnLocalScriptRoot = 'public/js/cdn';
|
|
|
const cdnLocalScriptWebRoot = '/js/cdn';
|
|
|
const cdnLocalStyleRoot = 'public/styles/cdn';
|
|
|
const cdnLocalStyleWebRoot = '/styles/cdn';
|
|
|
|
|
|
+
|
|
|
class CdnResourcesService {
|
|
|
constructor() {
|
|
|
- this.logger = require('@alias/logger')('growi:service:CdnResourcesResolver');
|
|
|
+ this.logger = require('@alias/logger')('growi:service:CdnResourcesService');
|
|
|
|
|
|
this.noCdn = !!process.env.NO_CDN;
|
|
|
- this.loadMetaData();
|
|
|
+ this.loadManifests();
|
|
|
}
|
|
|
|
|
|
- loadMetaData() {
|
|
|
- this.cdnResources = require('@root/resource/cdn-resources');
|
|
|
- this.logger.debug('meta data loaded : ', this.cdnResources);
|
|
|
+ loadManifests() {
|
|
|
+ this.cdnManifests = require('@root/resource/cdn-manifests');
|
|
|
+ this.logger.debug('manifest data loaded : ', this.cdnManifests);
|
|
|
}
|
|
|
|
|
|
- async downloadAndWrite(url, file, replacestream) {
|
|
|
- // get
|
|
|
- const response = await axios.get(url, { responseType: 'stream' });
|
|
|
- // replace and write
|
|
|
- let stream = response.data;
|
|
|
- if (replacestream != null) {
|
|
|
- stream = response.data.pipe(replacestream);
|
|
|
- }
|
|
|
- return stream.pipe(fs.createWriteStream(file));
|
|
|
- }
|
|
|
+ async downloadAndWriteAll() {
|
|
|
+ const downloader = new CdnResourcesDownloader();
|
|
|
|
|
|
- async downloadAndWriteScripts() {
|
|
|
- const promisesForScript = this.cdnResources.js.map(resource => {
|
|
|
- return this.downloadAndWrite(
|
|
|
- resource.url,
|
|
|
- helpers.root(cdnLocalScriptRoot, `${resource.name}.js`));
|
|
|
+ const cdnScriptResources = this.cdnManifests.js.map(manifest => {
|
|
|
+ const outDir = helpers.root(cdnLocalScriptRoot);
|
|
|
+ return new CdnResource(manifest.name, manifest.url, outDir);
|
|
|
});
|
|
|
-
|
|
|
- return Promise.all(promisesForScript);
|
|
|
- }
|
|
|
-
|
|
|
- async downloadAndWriteStyles() {
|
|
|
- // styles
|
|
|
- const assets = [];
|
|
|
- const promisesForStyle = this.cdnResources.style.map(resource => {
|
|
|
- const urlReplacer = replaceStream(
|
|
|
- /url\((?!"data:)["']?(.+?)["']?\)/g, // https://regex101.com/r/Sds38A/2
|
|
|
- (match, relativeUrl) => {
|
|
|
- // get basename
|
|
|
- const parsedUrl = new URL(relativeUrl, resource.url);
|
|
|
- const basename = path.basename(parsedUrl.pathname);
|
|
|
-
|
|
|
- // add assets metadata to download later
|
|
|
- assets.push({
|
|
|
- url: parsedUrl.toString(),
|
|
|
- dir: helpers.root(cdnLocalStyleRoot, resource.name),
|
|
|
- basename: basename,
|
|
|
- });
|
|
|
-
|
|
|
- const replaceUrl = urljoin(cdnLocalStyleWebRoot, resource.name, basename);
|
|
|
- return `url(${replaceUrl})`;
|
|
|
- });
|
|
|
-
|
|
|
- return this.downloadAndWrite(
|
|
|
- resource.url,
|
|
|
- helpers.root(cdnLocalStyleRoot, `${resource.name}.css`),
|
|
|
- urlReplacer);
|
|
|
+ const cdnStyleResources = this.cdnManifests.style.map(manifest => {
|
|
|
+ const outDir = helpers.root(cdnLocalStyleRoot);
|
|
|
+ return new CdnResource(manifest.name, manifest.url, outDir);
|
|
|
});
|
|
|
|
|
|
- await Promise.all(promisesForStyle);
|
|
|
-
|
|
|
- // assets in css
|
|
|
- const promisesForAssets = assets.map(resource => {
|
|
|
- // create dir if dir does not exist
|
|
|
- if (!fs.existsSync(resource.dir)) {
|
|
|
- fs.mkdirSync(resource.dir);
|
|
|
+ const dlStylesOptions = {
|
|
|
+ replaceUrl: {
|
|
|
+ webroot: cdnLocalStyleWebRoot,
|
|
|
}
|
|
|
+ };
|
|
|
|
|
|
- return this.downloadAndWrite(
|
|
|
- resource.url,
|
|
|
- path.join(resource.dir, resource.basename));
|
|
|
- });
|
|
|
-
|
|
|
- return Promise.all(promisesForAssets);
|
|
|
- }
|
|
|
-
|
|
|
- async downloadAndWriteAll() {
|
|
|
return Promise.all([
|
|
|
- this.downloadAndWriteScripts(),
|
|
|
- this.downloadAndWriteStyles(),
|
|
|
+ downloader.downloadScripts(cdnScriptResources),
|
|
|
+ downloader.downloadStyles(cdnStyleResources, dlStylesOptions),
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Generate script tag string
|
|
|
*
|
|
|
- * @param {Object} resource
|
|
|
+ * @param {Object} manifest
|
|
|
* @param {boolean} noCdn
|
|
|
*/
|
|
|
- generateScriptTag(resource, noCdn) {
|
|
|
+ generateScriptTag(manifest, noCdn) {
|
|
|
const attrs = [];
|
|
|
- const args = resource.args || {};
|
|
|
+ const args = manifest.args || {};
|
|
|
|
|
|
if (args.async) {
|
|
|
attrs.push('async');
|
|
|
@@ -117,39 +68,39 @@ class CdnResourcesService {
|
|
|
// TODO process integrity
|
|
|
|
|
|
const url = noCdn
|
|
|
- ? urljoin(cdnLocalScriptWebRoot, resource.name) + '.js'
|
|
|
- : resource.url;
|
|
|
+ ? urljoin(cdnLocalScriptWebRoot, manifest.name) + '.js'
|
|
|
+ : manifest.url;
|
|
|
return `<script src="${url}" ${attrs.join(' ')}></script>`;
|
|
|
}
|
|
|
|
|
|
getScriptTagByName(name) {
|
|
|
- const tags = this.cdnResources.js
|
|
|
- .filter(resource => resource.name === name)
|
|
|
- .map(resource => {
|
|
|
- return this.generateScriptTag(resource, this.noCdn);
|
|
|
+ const tags = this.cdnManifests.js
|
|
|
+ .filter(manifest => manifest.name === name)
|
|
|
+ .map(manifest => {
|
|
|
+ return this.generateScriptTag(manifest, this.noCdn);
|
|
|
});
|
|
|
return tags[0];
|
|
|
}
|
|
|
|
|
|
getScriptTagsByGroup(group) {
|
|
|
- return this.cdnResources.js
|
|
|
- .filter(resource => {
|
|
|
- return resource.groups != null && resource.groups.includes(group);
|
|
|
+ return this.cdnManifests.js
|
|
|
+ .filter(manifest => {
|
|
|
+ return manifest.groups != null && manifest.groups.includes(group);
|
|
|
})
|
|
|
- .map(resource => {
|
|
|
- return this.generateScriptTag(resource, this.noCdn);
|
|
|
+ .map(manifest => {
|
|
|
+ return this.generateScriptTag(manifest, this.noCdn);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Generate style tag string
|
|
|
*
|
|
|
- * @param {Object} resource
|
|
|
+ * @param {Object} manifest
|
|
|
* @param {boolean} noCdn
|
|
|
*/
|
|
|
- generateStyleTag(resource, noCdn) {
|
|
|
+ generateStyleTag(manifest, noCdn) {
|
|
|
const attrs = [];
|
|
|
- const args = resource.args || {};
|
|
|
+ const args = manifest.args || {};
|
|
|
|
|
|
if (args.async) {
|
|
|
attrs.push('async');
|
|
|
@@ -161,28 +112,28 @@ class CdnResourcesService {
|
|
|
// TODO process integrity
|
|
|
|
|
|
const url = noCdn
|
|
|
- ? urljoin(cdnLocalStyleWebRoot, resource.name) + '.css'
|
|
|
- : resource.url;
|
|
|
+ ? urljoin(cdnLocalStyleWebRoot, manifest.name) + '.css'
|
|
|
+ : manifest.url;
|
|
|
|
|
|
return `<link rel="stylesheet" href="${url}" ${attrs.join(' ')}>`;
|
|
|
}
|
|
|
|
|
|
getStyleTagByName(name) {
|
|
|
- const tags = this.cdnResources.style
|
|
|
- .filter(resource => resource.name === name)
|
|
|
- .map(resource => {
|
|
|
- return this.generateStyleTag(resource, this.noCdn);
|
|
|
+ const tags = this.cdnManifests.style
|
|
|
+ .filter(manifest => manifest.name === name)
|
|
|
+ .map(manifest => {
|
|
|
+ return this.generateStyleTag(manifest, this.noCdn);
|
|
|
});
|
|
|
return tags[0];
|
|
|
}
|
|
|
|
|
|
getStyleTagsByGroup(group) {
|
|
|
- return this.cdnResources.style
|
|
|
- .filter(resource => {
|
|
|
- return resource.groups != null && resource.groups.includes(group);
|
|
|
+ return this.cdnManifests.style
|
|
|
+ .filter(manifest => {
|
|
|
+ return manifest.groups != null && manifest.groups.includes(group);
|
|
|
})
|
|
|
- .map(resource => {
|
|
|
- return this.generateStyleTag(resource, this.noCdn);
|
|
|
+ .map(manifest => {
|
|
|
+ return this.generateStyleTag(manifest, this.noCdn);
|
|
|
});
|
|
|
}
|
|
|
}
|