Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save icebob/1e64f795b7541d6c0f522153922f7dda to your computer and use it in GitHub Desktop.
Save icebob/1e64f795b7541d6c0f522153922f7dda to your computer and use it in GitHub Desktop.
webpack-moleculer-service-plugin
'use strict';
const path = require('path');
const fs = require('fs');
const YAML = require('yaml');
function WebpackMoleculerServicePlugin(options) {
options = options || {};
if (typeof options === 'string') {
this.options = { output: options };
} else {
this.options = options;
}
}
const DEFAULT_SERVICE_MASK = /^services\/.*\/.*service\.js$/;
const TEMPLATE = {
api: {
buildService(service, stackName) {
const {
service: serviceName,
image,
domain: { Host, PathPrefix, Port },
} = service;
const networks = {
[serviceName]: null,
traefik: {
external: true,
},
[`${stackName}_nats`]: {
external: true,
},
[`${stackName}_redis`]: {
external: true,
},
};
const rulePathPrefix = PathPrefix ? `;PathPrefix:${PathPrefix}` : '';
const traefikRule = `Host:${Host}${rulePathPrefix}`;
const services = {
[serviceName]: {
image,
command: ['yarn', 'start:prod', `services/${serviceName}`],
environment: {
NODE_ENV: 'production',
NODE_ID: `${stackName}_node_${serviceName}`,
},
secrets: [{ source: 'env', target: '/app/.env' }],
networks: Object.keys(networks),
logging: {
driver: 'json-file',
options: {
'max-size': '50m',
},
},
deploy: {
replicas: 2,
restart_policy: {
condition: 'on-failure',
max_attempts: 3,
},
update_config: {
parallelism: 2,
delay: '0s',
order: 'start-first',
},
labels: {
'traefik.backend': `${stackName}_${serviceName}`,
'traefik.enable': 'true',
'traefik.frontend.rule': traefikRule,
'traefik.port': Port || 80,
'traefik.protocol': 'http',
'traefik.docker.network': 'traefik',
},
},
},
};
const secrets = {
env: {
name: `${stackName}_env`,
external: true,
},
};
return {
version: '3.5',
services,
networks,
secrets,
};
},
},
service: {
buildService(service, stackName) {
const { service: serviceName, image } = service;
const networks = {
[`${stackName}_postgres`]: {
external: true,
},
[`${stackName}_nats`]: {
external: true,
},
[`${stackName}_redis`]: {
external: true,
},
};
const services = {
[serviceName]: {
image,
command: ['yarn', 'start:prod', `services/${serviceName}`],
environment: {
NODE_ENV: 'production',
NODE_ID: `${stackName}_node_${serviceName}`,
},
secrets: [{ source: 'env', target: '/app/.env' }],
networks: Object.keys(networks),
logging: {
driver: 'json-file',
options: {
'max-size': '50m',
},
},
deploy: {
update_config: {
parallelism: 2,
delay: '0s',
order: 'start-first',
},
},
},
};
const secrets = {
env: {
name: `${stackName}_env`,
external: true,
},
};
return {
version: '3.5',
services,
networks,
secrets,
};
},
},
};
function getServiceName(serviceFile) {
return serviceFile.split('/')[1];
}
// function buildService(service) {
// }
/**
* Write Stack yml file to directory
*
* @param {*} stackName
* @param {*} service
* @param {*} toDir
*/
function writeStack(stackName, service, toDir) {
const { template, service: serviceName } = service;
const sTmpl = TEMPLATE[template];
const { buildService } = sTmpl;
const serviceYaml = YAML.stringify(buildService(service, stackName));
const composeFile = `${serviceName}.yml`;
const stackPath = path.resolve(toDir, composeFile);
fs.writeFileSync(stackPath, serviceYaml.replace(/: null/g, ':'));
return {
composeFile,
path: stackPath,
deploy: `docker stack deploy -c ./${composeFile} --with-registry-auth ${stackName}`,
};
}
WebpackMoleculerServicePlugin.prototype.apply = function(compiler) {
const options = this.options;
compiler.plugin('after-emit', function(compiler, callback) {
// Set output location
const { entries, mask = DEFAULT_SERVICE_MASK, output = compiler.options.output.path, image, stack } = options;
if (!image) {
console.warn('[WebpackMoleculerServicePlugin] There is no image mentioned');
return;
}
const servicePattern = mask instanceof RegExp ? mask : new RegExp(mask);
// Create output directory if not exist
if (!fs.existsSync(output)) {
fs.mkdirSync(output);
}
const serviceMap = {};
let serviceIdx = 0;
const assetKeys = Object.keys(compiler.assets);
const serviceFiles = assetKeys.filter(k => servicePattern.test(k));
const services = serviceFiles.map(f => {
const service = getServiceName(f);
serviceMap[service] = serviceIdx++;
return {
template: 'service',
service,
image,
};
});
let customServices = [];
if (entries && entries.length > 0) {
customServices = entries.reduce((carry, { template, service, options: { domain, image: eImage } }) => {
const s = {
template,
service,
image: eImage ? eImage : image,
domain,
};
if (serviceMap.hasOwnProperty(service)) {
const idx = serviceMap[service];
services[idx] = { ...s };
} else {
carry.push(s);
}
return carry;
}, []);
}
const stacks = [...services, ...customServices].map(s => writeStack(stack, s, output));
const deployCmdText = stacks.reduce((carry, s) => `${carry}${s.deploy}\n`, '');
fs.writeFileSync(path.resolve(output, 'deploy.sh'), deployCmdText);
callback();
});
};
module.exports = WebpackMoleculerServicePlugin;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment