From e987e8f483561a69e8346fc1f649ac5f33a89b58 Mon Sep 17 00:00:00 2001 From: Zach Rispoli Date: Wed, 19 Jul 2017 13:56:52 -0400 Subject: [PATCH] Bundle libraries when project is downloaded as zip (New approach) (#376) * External libraries are bundled with zip when project is downloaded (#44) * Fix linting errors * Add a check for valid URLs before trying to bundle a library into project * Add is-url lib to package.json --- package.json | 1 + server/controllers/project.controller.js | 52 ++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 1d77b9c0..bb328c95 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "fs-promise": "^1.0.0", "htmlhint": "^0.9.13", "is_js": "^0.9.0", + "is-url": "^1.2.2", "js-beautify": "^1.6.4", "jsdom": "^9.8.3", "jshint": "^2.9.4", diff --git a/server/controllers/project.controller.js b/server/controllers/project.controller.js index 8814740a..911eab1e 100644 --- a/server/controllers/project.controller.js +++ b/server/controllers/project.controller.js @@ -1,6 +1,8 @@ import archiver from 'archiver'; import request from 'request'; import moment from 'moment'; +import isUrl from 'is-url'; +import jsdom, { serializeDocument } from 'jsdom'; import Project from '../models/project'; import User from '../models/user'; import { deleteObjectsFromS3, getObjectKey } from './aws.controller'; @@ -100,8 +102,7 @@ function deleteFilesFromS3(files) { } return false; }) - .map(file => getObjectKey(file.url)) - ); + .map(file => getObjectKey(file.url))); } export function deleteProject(req, res) { @@ -169,6 +170,48 @@ export function getProjectsForUser(req, res) { } } +function bundleExternalLibs(project, zip, callback) { + const rootFile = project.files.find(file => file.name === 'root'); + const indexHtml = project.files.find(file => file.name === 'index.html'); + let numScriptsResolved = 0; + let numScriptTags = 0; + + function resolveScriptTagSrc(scriptTag, document) { + const path = scriptTag.src.split('/'); + const filename = path[path.length - 1]; + const src = scriptTag.src; + + if (!isUrl(src)) { + numScriptsResolved += 1; + return; + } + + request({ method: 'GET', url: src, encoding: null }, (err, response, body) => { + if (err) { + console.log(err); + } else { + zip.append(body, { name: filename }); + scriptTag.src = filename; + } + + numScriptsResolved += 1; + if (numScriptsResolved === numScriptTags) { + indexHtml.content = serializeDocument(document); + callback(); + } + }); + } + + jsdom.env(indexHtml.content, (innerErr, window) => { + const indexHtmlDoc = window.document; + const scriptTags = indexHtmlDoc.getElementsByTagName('script'); + numScriptTags = scriptTags.length; + for (let i = 0; i < numScriptTags; i += 1) { + resolveScriptTagSrc(scriptTags[i], indexHtmlDoc); + } + }); +} + function buildZip(project, req, res) { const zip = archiver('zip'); const rootFile = project.files.find(file => file.name === 'root'); @@ -208,7 +251,10 @@ function buildZip(project, req, res) { } } } - addFileToZip(rootFile, '/'); + + bundleExternalLibs(project, zip, () => { + addFileToZip(rootFile, '/'); + }); } export function downloadProjectAsZip(req, res) {