Skip to content
This repository has been archived by the owner on Jun 24, 2024. It is now read-only.

Commit

Permalink
feat: allow opting out of using worker for asset bundling
Browse files Browse the repository at this point in the history
Add a flag `bundleInProcess` that does asset bundling in-process instead of using a worker.

This is helpful when you don't want to clean up after the server is no longer used, particularly when testing a Podlet or Layout server with a local instance of the asset server.

Note: `bundler#endWorkers` is made async.
  • Loading branch information
theneva authored Jul 18, 2018
1 parent 2e47ae8 commit 9146902
Show file tree
Hide file tree
Showing 13 changed files with 286 additions and 246 deletions.
7 changes: 7 additions & 0 deletions lib/bundler-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

const bundleJS = require('@asset-pipe/js-reader');
const bundleCSS = require('@asset-pipe/css-reader');

module.exports.bundleJS = bundleJS;
module.exports.bundleCSS = bundleCSS;
134 changes: 130 additions & 4 deletions lib/bundler.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,133 @@
'use strict';

const bundleJS = require('@asset-pipe/js-reader');
const bundleCSS = require('@asset-pipe/css-reader');
const assert = require('assert');
const Boom = require('boom');
const { promisify } = require('util');
const body = promisify(require('body/json'));
const schemas = require('./schemas');
const parseJson = require('parse-json');
const { hasher } = require('@asset-pipe/common');
const { default: Worker } = require('jest-worker');

module.exports.bundleJS = bundleJS;
module.exports.bundleCSS = bundleCSS;
module.exports = class Bundler {
constructor({ bundleInProcess = false } = {}) {
this.bundleInProcess = bundleInProcess;

if (bundleInProcess) {
this.bundler = require('./bundler-utils');
} else {
this.bundler = new Worker(require.resolve('./bundler-utils'), {
numWorkers: 2,
});
}
}

async fetchFeeds(sink, ids) {
try {
return await Promise.all(
ids.map(async fileName => ({
fileName,
contents: await sink.get(fileName),
}))
);
} catch (err) {
throw Boom.boomify(err, {
message: 'Unable to fetch 1 or more feeds.',
});
}
}

parseFeedContent(feeds) {
const result = [];
for (const { fileName, contents } of feeds) {
try {
result.push(parseJson(contents));
} catch (err) {
throw Boom.boomify(err, {
message: `Unable to parse 1 or more feeds as JSON. File ${fileName} was unparseable.`,
});
}
}
return result;
}

async bundleFeeds(feeds, type, options) {
if (type === 'css') {
try {
return await this.bundler.bundleCSS(feeds);
} catch (err) {
throw Boom.boomify(err, {
message: 'Unable to bundle feeds as CSS.',
});
}
} else {
try {
return await this.bundler.bundleJS(feeds, options);
} catch (err) {
throw Boom.boomify(err, {
message: 'Unable to bundle feeds as JS.',
});
}
}
}

async upload(sink, fileName, content) {
try {
return await sink.set(fileName, content);
} catch (err) {
throw Boom.boomify(err, {
message: `Unable to upload file with name "${fileName}".`,
});
}
}

async bundleAndUpload({ sink, type, feedIds, uri, ...options }) {
assert(
Array.isArray(feedIds) && feedIds.length > 0,
`Expected at least 1 feed id, but got ${feedIds}`
);

const fetchedFeeds = await this.fetchFeeds(sink, feedIds, 3);
const parsedFeeds = this.parseFeedContent(fetchedFeeds);
const content = await this.bundleFeeds(parsedFeeds, type, options);
const fileName = `${hasher(content)}.${type}`;
await this.upload(sink, fileName, content, 3);

return {
file: fileName,
uri: uri + fileName,
};
}

async parseBody(req, res) {
try {
return await body(req, res, {});
} catch (e) {
throw Boom.boomify(e, {
statusCode: 400,
message:
'Unparsable feed data given in POST-body. Invalid or empty JSON payload.',
});
}
}

validateFeeds(feeds) {
const result = schemas.ids.validate(feeds);
if (result.error) {
throw Boom.boomify(result.error, {
statusCode: 400,
message: 'Invalid feed data given in POST-body.',
});
}
return result.value;
}

async endWorkers() {
if (this.bundleInProcess) {
return false;
}

await this.bundler.end();
return true;
}
};
21 changes: 8 additions & 13 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,7 @@ const { Transform } = require('readable-stream');
const params = require('./params');
const MetaStorage = require('./meta-storage');
const Metrics = require('@metrics/client');
const {
parseBody,
validateFeeds,
bundleAndUpload,
endWorkers,
booleanWithDefault,
} = require('./utils');
const { booleanWithDefault } = require('./utils');
const OptimisticBundler = require('../lib/optimistic-bundler');

const DEFAULT_NODE_ENV = 'development';
Expand Down Expand Up @@ -113,7 +107,7 @@ class CheckEmptyPayload extends Transform {
}

module.exports = class Router extends EventEmitter {
constructor(sink, options = {}) {
constructor(sink, { bundleInProcess, ...options } = {}) {
super();

const { NODE_ENV } = process.env;
Expand All @@ -130,6 +124,7 @@ module.exports = class Router extends EventEmitter {
this.metrics = new Metrics();

this.bundler = new OptimisticBundler({
bundleInProcess,
env: this.options.env,
sink: this.sink,
logger: this.options.logger,
Expand Down Expand Up @@ -197,7 +192,7 @@ module.exports = class Router extends EventEmitter {

this.app.post('/publish-instructions', async (req, res, next) => {
try {
const payload = await parseBody(req, res);
const payload = await this.bundler.parseBody(req, res);
await this.bundler.publishInstructions(payload, {
minify: booleanWithDefault(
req.query.minify,
Expand Down Expand Up @@ -311,7 +306,7 @@ module.exports = class Router extends EventEmitter {

try {
this.emit('info', `Parsing raw feed data from body`);
const payload = await parseBody(req, res);
const payload = await this.bundler.parseBody(req, res);
this.emit(
'info',
`Successfully parsed feed data from request body. Result: ${JSON.stringify(
Expand All @@ -320,13 +315,13 @@ module.exports = class Router extends EventEmitter {
);

this.emit('info', `Validating parsed feed data against schema`);
const feedIds = validateFeeds(payload);
const feedIds = this.bundler.validateFeeds(payload);

this.emit(
'info',
`Producing and saving asset bundle for requested feeds`
);
const response = await bundleAndUpload({
const response = await this.bundler.bundleAndUpload({
sink: this.sink,
feedIds,
uri: this.buildUri('bundle', req.headers.host, req.secure),
Expand Down Expand Up @@ -461,6 +456,6 @@ module.exports = class Router extends EventEmitter {

/* istanbul ignore next: invoking this method in the test has repercussions */
async cleanup() {
endWorkers();
await this.bundler.endWorkers();
}
};
12 changes: 7 additions & 5 deletions lib/optimistic-bundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

const Storage = require('./storage');
const { hashContent } = require('./hasher');
const { bundleFeeds } = require('./utils');
const Joi = require('joi');
const schemas = require('../lib/schemas');
const abslog = require('abslog');
const { hashArray } = require('@asset-pipe/common');
const Metrics = require('@metrics/client');
const Bundler = require('./bundler');

const PERSIST_TO_STORAGE_METRIC = {
name: 'asset_server_persist_to_storage_timer',
Expand All @@ -24,8 +24,10 @@ const EXISTS_IN_STORAGE_METRIC = {
description: 'Time taken for a check for existence operation from storage',
};

module.exports = class OptimisticBundler {
constructor(options) {
module.exports = class OptimisticBundler extends Bundler {
constructor({ bundleInProcess, ...options }) {
super({ bundleInProcess });

const opts = {
sourceMaps: false,
minify: false,
Expand Down Expand Up @@ -172,7 +174,7 @@ module.exports = class OptimisticBundler {
},
});

const content = await bundleFeeds(feeds, type, {
const content = await super.bundleFeeds(feeds, type, {
...this.options,
...this.overrides,
});
Expand Down Expand Up @@ -304,7 +306,7 @@ module.exports = class OptimisticBundler {
`${type} fallback bundle for tag "${tag}" already exists as "${hash}.${type}" and will not be published`
);
} else {
const bundle = await bundleFeeds([feed], type);
const bundle = await super.bundleFeeds([feed], type);
await this.setBundle(hash, type, bundle);
this.log.info(
`${type} fallback bundle for tag "${tag}" published as "${hash}.${type}"`
Expand Down
Loading

0 comments on commit 9146902

Please sign in to comment.