Created
July 24, 2019 16:22
-
-
Save lucwj/837a23b6ae6393746f1420111dc013ce 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