-
-
Save icebob/1e64f795b7541d6c0f522153922f7dda to your computer and use it in GitHub Desktop.
webpack-moleculer-service-plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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