serverless-webpack
Advanced tools
Comparing version 2.2.0 to 3.0.0-rc.1
85
index.js
'use strict'; | ||
const BbPromise = require('bluebird'); | ||
const _ = require('lodash'); | ||
const validate = require('./lib/validate'); | ||
const compile = require('./lib/compile'); | ||
const copyFunctionArtifact = require('./lib/copyFunctionArtifact'); | ||
const wpwatch = require('./lib/wpwatch'); | ||
const cleanup = require('./lib/cleanup'); | ||
const run = require('./lib/run'); | ||
const serve = require('./lib/serve'); | ||
const prepareLocalInvoke = require('./lib/prepareLocalInvoke'); | ||
const packExternalModules = require('./lib/packExternalModules'); | ||
const packageModules = require('./lib/packageModules'); | ||
const lib = require('./lib'); | ||
@@ -26,6 +27,4 @@ | ||
if ( | ||
this.serverless.service | ||
&& this.serverless.service.custom | ||
&& this.serverless.service.custom.webpack | ||
&& this.serverless.service.custom.webpack.endsWith('.ts') | ||
_.has(this.serverless, 'service.custom.webpack') && | ||
_.endsWith(this.serverless.service.custom.webpack, '.ts') | ||
) { | ||
@@ -35,12 +34,12 @@ require('ts-node/register'); | ||
Object.assign( | ||
_.assign( | ||
this, | ||
validate, | ||
compile, | ||
copyFunctionArtifact, | ||
wpwatch, | ||
cleanup, | ||
run, | ||
serve, | ||
packExternalModules | ||
packExternalModules, | ||
packageModules, | ||
prepareLocalInvoke | ||
); | ||
@@ -67,13 +66,2 @@ | ||
], | ||
options: { | ||
function: { | ||
usage: 'Name of the function', | ||
shortcut: 'f', | ||
required: true, | ||
}, | ||
path: { | ||
usage: 'Path to JSON file holding input data', | ||
shortcut: 'p', | ||
}, | ||
}, | ||
}, | ||
@@ -85,13 +73,2 @@ watch: { | ||
], | ||
options: { | ||
function: { | ||
usage: 'Name of the function', | ||
shortcut: 'f', | ||
required: true, | ||
}, | ||
path: { | ||
usage: 'Path to JSON file holding input data', | ||
shortcut: 'p', | ||
}, | ||
}, | ||
}, | ||
@@ -103,8 +80,2 @@ serve: { | ||
], | ||
options: { | ||
port: { | ||
usage: 'The local server port', | ||
shortcut: 'p', | ||
}, | ||
}, | ||
}, | ||
@@ -119,3 +90,4 @@ }, | ||
.then(this.compile) | ||
.then(this.packExternalModules), | ||
.then(this.packExternalModules) | ||
.then(this.packageModules), | ||
@@ -128,7 +100,18 @@ 'after:deploy:createDeploymentArtifacts': () => BbPromise.bind(this) | ||
.then(this.compile) | ||
.then(this.packExternalModules), | ||
.then(this.packExternalModules) | ||
.then(this.packageModules), | ||
'after:deploy:function:packageFunction': () => BbPromise.bind(this) | ||
.then(this.copyFunctionArtifact), | ||
'before:invoke:local:invoke': () => BbPromise.bind(this) | ||
.then(this.validate) | ||
.then(this.compile) | ||
.then(this.prepareLocalInvoke), | ||
'after:invoke:local:invoke': () => BbPromise.bind(this) | ||
.then(() => { | ||
if (this.options.watch && !this.isWatching) { | ||
return this.watch(); | ||
} | ||
return BbPromise.resolve(); | ||
}), | ||
'webpack:validate': () => BbPromise.bind(this) | ||
@@ -139,17 +122,13 @@ .then(this.validate), | ||
.then(this.compile) | ||
.then(this.packExternalModules), | ||
.then(this.packExternalModules) | ||
.then(this.packageModules), | ||
'webpack:invoke:invoke': () => BbPromise.bind(this) | ||
.then(this.validate) | ||
.then(this.compile) | ||
.then(this.run) | ||
.then(out => this.serverless.cli.consoleLog(out)), | ||
.then(() => BbPromise.reject(new this.serverless.classes.Error('Use "serverless invoke local" instead.'))), | ||
'webpack:watch:watch': () => BbPromise.bind(this) | ||
.then(this.validate) | ||
.then(this.watch), | ||
.then(() => BbPromise.reject(new this.serverless.classes.Error('Use "serverless invoke local --watch" instead.'))), | ||
'webpack:serve:serve': () => BbPromise.bind(this) | ||
.then(this.validate) | ||
.then(this.serve), | ||
.then(() => BbPromise.reject(new this.serverless.classes.Error('serve has been removed. Use serverless-offline instead.'))), | ||
@@ -160,3 +139,3 @@ 'before:offline:start': () => BbPromise.bind(this) | ||
.then(this.wpwatch), | ||
'before:offline:start:init': () => BbPromise.bind(this) | ||
@@ -166,3 +145,3 @@ .then(this.validate) | ||
.then(this.wpwatch), | ||
}; | ||
@@ -169,0 +148,0 @@ } |
'use strict'; | ||
const BbPromise = require('bluebird'); | ||
const path = require('path'); | ||
const fse = require('fs-extra'); | ||
const _ = require('lodash'); | ||
@@ -12,54 +10,10 @@ module.exports = { | ||
const moveArtifact = new BbPromise((resolve, reject) => { | ||
if (this.originalServicePath) { | ||
this.serverless.config.servicePath = this.originalServicePath; | ||
fse.copy( | ||
path.join(webpackOutputPath, '.serverless'), | ||
path.join(this.serverless.config.servicePath, '.serverless'), | ||
(err) => { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
if (this.serverless.service.package.individually) { | ||
const functionNames = this.serverless.service.getAllFunctions(); | ||
this.options.verbose && this.serverless.cli.log(`Remove ${webpackOutputPath}`); | ||
_.forEach(functionNames, name => { | ||
const func = this.serverless.service.getFunction(name); | ||
// The location of the artifact property changed from Serverless | ||
// 1.17 to 1.18 (damn, the guys should use SemVer!). We have to | ||
// use the new one if available here. | ||
const artifactParent = func.artifact ? func : func.package; | ||
if (this.serverless.utils.dirExistsSync(webpackOutputPath)) { | ||
fse.removeSync(webpackOutputPath); | ||
} | ||
artifactParent.artifact = path.join( | ||
this.serverless.config.servicePath, | ||
'.serverless', | ||
path.basename(artifactParent.artifact) | ||
); | ||
}); | ||
resolve(); | ||
return; | ||
} | ||
this.serverless.service.package.artifact = path.join( | ||
this.serverless.config.servicePath, | ||
'.serverless', | ||
path.basename(this.serverless.service.package.artifact) | ||
); | ||
resolve(); | ||
} | ||
} | ||
); | ||
} else { | ||
resolve(); | ||
} | ||
}); | ||
return moveArtifact | ||
.then(() => { | ||
if (this.serverless.utils.dirExistsSync(webpackOutputPath)) { | ||
fse.removeSync(webpackOutputPath); | ||
} | ||
}) | ||
.then(() => BbPromise.resolve()); | ||
return BbPromise.resolve(); | ||
}, | ||
}; |
'use strict'; | ||
const _ = require('lodash'); | ||
const BbPromise = require('bluebird'); | ||
@@ -16,16 +17,26 @@ const webpack = require('webpack'); | ||
this.serverless.cli.consoleLog(stats.toString({ | ||
colors: true, | ||
hash: false, | ||
version: false, | ||
chunks: false, | ||
children: false | ||
})); | ||
if (stats.compilation.errors.length) { | ||
throw new Error('Webpack compilation error, see above'); | ||
if (!this.multiCompile) { | ||
stats = { stats: [stats] }; | ||
} | ||
const outputPath = stats.compilation.compiler.outputPath; | ||
this.webpackOutputPath = outputPath; | ||
this.originalServicePath = this.serverless.config.servicePath; | ||
this.serverless.config.servicePath = outputPath; | ||
const compileOutputPaths = []; | ||
_.forEach(stats.stats, compileStats => { | ||
this.serverless.cli.consoleLog(compileStats.toString({ | ||
colors: true, | ||
hash: false, | ||
version: false, | ||
chunks: false, | ||
children: false | ||
})); | ||
if (compileStats.compilation.errors.length) { | ||
throw new Error('Webpack compilation error, see above'); | ||
} | ||
compileOutputPaths.push(compileStats.compilation.compiler.outputPath); | ||
}); | ||
this.compileOutputPaths = compileOutputPaths; | ||
return stats; | ||
@@ -32,0 +43,0 @@ }); |
@@ -5,2 +5,2 @@ 'use strict'; | ||
entries: {} | ||
} | ||
}; |
'use strict'; | ||
const BbPromise = require('bluebird'); | ||
const fs = require('fs'); | ||
const _ = require('lodash'); | ||
const path = require('path'); | ||
const childProcess = require('child_process'); | ||
const fse = require('fs-extra'); | ||
const npm = require('npm-programmatic'); | ||
const isBuiltinModule = require('is-builtin-module'); | ||
function getProdModules(externalModules, packagePath) { | ||
const packageJson = require(path.join(process.cwd(), packagePath)); | ||
function getProdModules(externalModules, packagePath, dependencyGraph) { | ||
const packageJsonPath = path.join(process.cwd(), packagePath); | ||
const packageJson = require(packageJsonPath); | ||
const prodModules = []; | ||
@@ -16,11 +18,19 @@ | ||
if (!packageJson.dependencies) { | ||
return [] | ||
return []; | ||
} | ||
externalModules.forEach(module => { | ||
// Get versions of all transient modules | ||
_.forEach(externalModules, module => { | ||
let moduleVersion = packageJson.dependencies[module.external]; | ||
const moduleVersion = packageJson.dependencies[module]; | ||
if (moduleVersion) { | ||
prodModules.push(`${module}@${moduleVersion}`); | ||
prodModules.push(`${module.external}@${moduleVersion}`); | ||
} else if (!_.has(packageJson, `devDependencies.${module.external}`)) { | ||
// Add transient dependencies if they appear not in the service's dev dependencies | ||
const originInfo = _.get(dependencyGraph, `dependencies.${module.origin}`, {}); | ||
moduleVersion = _.get(originInfo, `dependencies.${module.external}.version`); | ||
if (!moduleVersion) { | ||
this.serverless.cli.log(`WARNING: Could not determine version of module ${module.external}`); | ||
} | ||
prodModules.push(moduleVersion ? `${module.external}@${moduleVersion}` : module.external); | ||
} | ||
@@ -33,8 +43,4 @@ }); | ||
function getExternalModuleName(module) { | ||
const path = /^external "(.*)"$/.exec(module.identifier())[1]; | ||
const pathComponents = path.split('/'); | ||
const main = pathComponents[0]; | ||
@@ -44,22 +50,35 @@ | ||
if (main.charAt(0) == '@') { | ||
return `${main}/${pathComponents[1]}` | ||
return `${main}/${pathComponents[1]}`; | ||
} | ||
return main | ||
return main; | ||
} | ||
function isExternalModule(module) { | ||
return module.identifier().indexOf('external ') === 0; | ||
return _.startsWith(module.identifier(), 'external ') && !isBuiltinModule(getExternalModuleName(module)); | ||
} | ||
/** | ||
* Find the original module that required the transient dependency. Returns | ||
* undefined if the module is a first level dependency. | ||
* @param {Object} issuer - Module issuer | ||
*/ | ||
function findExternalOrigin(issuer) { | ||
if (!_.isNil(issuer) && _.startsWith(issuer.rawRequest, './')) { | ||
return findExternalOrigin(issuer.issuer); | ||
} | ||
return issuer; | ||
} | ||
function getExternalModules(stats) { | ||
const externals = new Set(); | ||
stats.compilation.chunks.forEach(function(chunk) { | ||
_.forEach(stats.compilation.chunks, chunk => { | ||
// Explore each module within the chunk (built inputs): | ||
chunk.modules.forEach(function(module) { | ||
// Explore each source file path that was included into the module: | ||
_.forEach(chunk.modules, module => { | ||
if (isExternalModule(module)) { | ||
externals.add(getExternalModuleName(module)); | ||
externals.add({ | ||
origin: _.get(findExternalOrigin(module.issuer), 'rawRequest'), | ||
external: getExternalModuleName(module) | ||
}); | ||
} | ||
@@ -73,2 +92,15 @@ }); | ||
module.exports = { | ||
/** | ||
* We need a performant algorithm to install the packages for each single | ||
* function (in case we package individually). | ||
* (1) We fetch ALL packages needed by ALL functions in a first step | ||
* and use this as a base npm checkout. The checkout will be done to a | ||
* separate temporary directory with a package.json that contains everything. | ||
* (2) For each single compile we copy the whole node_modules to the compile | ||
* directory and create a (function) compile specific package.json and store | ||
* it in the compile directory. Now we start npm again there, and npm will just | ||
* remove the superfluous packages and optimize the remaining dependencies. | ||
* This will utilize the npm cache at its best and give us the needed results | ||
* and performance. | ||
*/ | ||
packExternalModules(stats) { | ||
@@ -81,40 +113,89 @@ | ||
return BbPromise.resolve().then(() => { | ||
if (!includes) { | ||
return BbPromise.resolve(stats); | ||
} | ||
if (!includes) { | ||
return; | ||
} | ||
const packagePath = includes.packagePath || './package.json'; | ||
const packageJsonPath = path.join(process.cwd(), packagePath); | ||
const packagePath = includes.packagePath || './package.json'; | ||
this.options.verbose && this.serverless.cli.log(`Fetch dependency graph from ${packageJsonPath}`); | ||
// Get first level dependency graph | ||
const command = 'npm ls -prod -json -depth=1'; // Only prod dependencies | ||
let dependencyGraph = {}; | ||
try { | ||
const depJson = childProcess.execSync(command, { | ||
cwd: path.dirname(packageJsonPath), | ||
maxBuffer: this.serverless.service.custom.packExternalModulesMaxBuffer || 200 * 1024, | ||
encoding: 'utf8' | ||
}); | ||
dependencyGraph = JSON.parse(depJson); | ||
} catch (e) { | ||
// We rethrow here. It's not recoverable. | ||
throw e; | ||
} | ||
const externalModules = getExternalModules(stats); | ||
// (1) Generate dependency composition | ||
const compositeModules = _.uniq(_.flatMap(stats.stats, compileStats => { | ||
const externalModules = getExternalModules.call(this, compileStats); | ||
return getProdModules.call(this, externalModules, packagePath, dependencyGraph); | ||
})); | ||
// this plugin will only install modules stated in dependencies section of package.json | ||
const prodModules = getProdModules(externalModules, packagePath); | ||
// (1.a) Install all needed modules | ||
const compositeModulePath = path.join(this.webpackOutputPath, 'dependencies'); | ||
const compositePackageJson = path.join(compositeModulePath, 'package.json'); | ||
this.serverless.utils.writeFileSync(compositePackageJson, '{}'); | ||
if (prodModules.length === 0) { | ||
return; | ||
} | ||
this.serverless.cli.log('Packing external modules: ' + compositeModules.join(', ')); | ||
this.serverless.cli.log('Packing external modules: ' + prodModules.join(", ")); | ||
return new BbPromise((resolve, reject) => { | ||
const start = _.now(); | ||
npm.install(compositeModules, { | ||
cwd: compositeModulePath, | ||
maxBuffer: this.serverless.service.custom.packExternalModulesMaxBuffer || 200 * 1024, | ||
save: true | ||
}).then(() => { | ||
this.options.verbose && this.serverless.cli.log(`Package took [${_.now() - start} ms]`); // eslint-disable-line promise/always-return | ||
resolve(stats.stats); | ||
}).catch(e => { | ||
reject(e); | ||
}); | ||
}) | ||
.mapSeries(compileStats => { | ||
const modulePath = compileStats.compilation.compiler.outputPath; | ||
const tmpPackageJson = path.join(this.serverless.config.servicePath, 'package.json'); | ||
// create a temp package.json in dist directory so that we can install the dependencies later. | ||
fs.writeFileSync(tmpPackageJson, "{}"); | ||
// Create package.json | ||
const modulePackageJson = path.join(modulePath, 'package.json'); | ||
const modulePackage = { | ||
dependencies: {} | ||
}; | ||
const prodModules = getProdModules.call(this, getExternalModules.call(this, compileStats), packagePath, dependencyGraph); | ||
_.forEach(prodModules, prodModule => { | ||
const splitModule = _.split(prodModule, '@'); | ||
// If we have a scoped module we have to re-add the @ | ||
if (_.startsWith(prodModule, '@')) { | ||
splitModule.splice(0, 1); | ||
splitModule[0] = '@' + splitModule[0]; | ||
} | ||
const moduleVersion = _.join(_.tail(splitModule), '@'); | ||
modulePackage.dependencies[_.first(splitModule)] = moduleVersion; | ||
}); | ||
this.serverless.utils.writeFileSync(modulePackageJson, JSON.stringify(modulePackage, null, 2)); | ||
return new BbPromise((resolve, reject) => { | ||
npm.install(prodModules, { | ||
cwd: this.serverless.config.servicePath, | ||
save: true | ||
}).then(() => { | ||
// fs.unlink(tmpPackageJson); | ||
resolve() | ||
}).catch(e => { | ||
// fs.unlink(tmpPackageJson); | ||
reject(e); | ||
// Copy modules | ||
const startCopy = _.now(); | ||
return BbPromise.fromCallback(callback => fse.copy(path.join(compositeModulePath, 'node_modules'), path.join(modulePath, 'node_modules'), callback)) | ||
.tap(() => this.options.verbose && this.serverless.cli.log(`Copy modules: ${modulePath} [${_.now() - startCopy} ms]`)) | ||
.then(() => { | ||
// Prune extraneous packages - removes not needed ones | ||
const startPrune = _.now(); | ||
return BbPromise.fromCallback(callback => { | ||
childProcess.exec('npm prune', { | ||
cwd: modulePath | ||
}, callback); | ||
}) | ||
}) | ||
}); | ||
.tap(() => this.options.verbose && this.serverless.cli.log(`Prune: ${modulePath} [${_.now() - startPrune} ms]`)); | ||
}); | ||
}) | ||
.return(stats); | ||
} | ||
}; |
@@ -9,71 +9,2 @@ 'use strict'; | ||
module.exports = { | ||
loadHandler(stats, functionId, purge) { | ||
const handler = this.serverless.service.functions[functionId].handler.split('.'); | ||
const moduleFileName = `${handler[0]}.js`; | ||
const handlerFilePath = path.join( | ||
path.resolve(stats.compilation.options.output.path), | ||
moduleFileName | ||
); | ||
if (purge) { | ||
utils.purgeCache(handlerFilePath); | ||
} | ||
//set environment before requiring, as imported modules will be immediately | ||
this.setEnvironmentVars(functionId); | ||
const module = require(handlerFilePath); | ||
const functionObjectPath = handler.slice(1); | ||
let func = module; | ||
for (let p of functionObjectPath) { | ||
func = func[p]; | ||
} | ||
return func; | ||
}, | ||
getEvent() { | ||
return this.options.path | ||
? this.serverless.utils.readFileSync(this.options.path) | ||
: null; // TODO use get-stdin instead | ||
}, | ||
getContext(functionName) { | ||
return { | ||
awsRequestId: utils.guid(), | ||
invokeid: utils.guid(), | ||
logGroupName: `/aws/lambda/${functionName}`, | ||
logStreamName: '2016/02/14/[HEAD]13370a84ca4ed8b77c427af260', | ||
functionVersion: '$LATEST', | ||
isDefaultFunctionVersion: true, | ||
functionName: functionName, | ||
memoryLimitInMB: '1024', | ||
}; | ||
}, | ||
setEnvironmentVars(functionName) { | ||
const providerEnvVars = this.serverless.service.provider.environment || {}; | ||
const functionEnvVars = this.serverless.service.functions[functionName].environment || {}; | ||
Object.assign(process.env, providerEnvVars, functionEnvVars); | ||
}, | ||
run(stats) { | ||
const functionName = this.options.function; | ||
this.serverless.cli.log(`Run function ${functionName}...`); | ||
const handler = this.loadHandler(stats, functionName); | ||
const event = this.getEvent(); | ||
const context = this.getContext(functionName); | ||
return new BbPromise((resolve, reject) => handler( | ||
event, | ||
context, | ||
(err, res) => { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
resolve(res); | ||
} | ||
} | ||
)); | ||
}, | ||
watch() { | ||
@@ -89,22 +20,20 @@ const functionName = this.options.function; | ||
} | ||
this.serverless.cli.log(`Run function ${functionName}...`); | ||
const handler = this.loadHandler(stats, functionName, true); | ||
const event = this.getEvent(); | ||
const context = this.getContext(functionName); | ||
handler( | ||
event, | ||
context, | ||
(err, res) => { | ||
if (err) { | ||
throw err; | ||
} else { | ||
this.serverless.cli.consoleLog(res); | ||
} | ||
BbPromise.try(() => { | ||
if (this.originalServicePath) { | ||
process.chdir(this.originalServicePath); | ||
this.serverless.config.servicePath = this.originalServicePath; | ||
} | ||
) | ||
if (!this.isWatching) { | ||
this.isWatching = true; | ||
return BbPromise.resolve(); | ||
} | ||
this.serverless.cli.log('Sources changed.'); | ||
return this.serverless.pluginManager.spawn('invoke:local'); | ||
}) | ||
.then(() => this.serverless.cli.log('Waiting for changes ...')); | ||
}); | ||
return BbPromise.resolve(); | ||
}, | ||
}; |
@@ -49,3 +49,3 @@ 'use strict'; | ||
return path.extname(_.first(sortedFiles)); | ||
} | ||
}; | ||
@@ -70,7 +70,8 @@ const getEntryForFunction = serverlessFunction => { | ||
// Expose entries - must be done before requiring the webpack configuration | ||
// Expose entries - must be done before requiring the webpack configuration | ||
const entries = {}; | ||
const functions = this.serverless.service.getAllFunctions(); | ||
if (this.options.function) { | ||
const serverlessFunction = this.serverless.service.getFunction(this.options.function); | ||
const serverlessFunction = this.serverless.service.getFunction(this.options.function); | ||
const entry = getEntryForFunction(serverlessFunction); | ||
@@ -84,3 +85,2 @@ _.merge(entries, entry); | ||
} | ||
lib.entries = entries; | ||
@@ -90,2 +90,3 @@ // Expose service file and options | ||
lib.options = this.options; | ||
lib.entries = entries; | ||
@@ -107,14 +108,8 @@ if (_.isString(this.webpackConfig)) { | ||
// Default output | ||
if (!this.webpackConfig.output) { | ||
if (!this.webpackConfig.output || _.isEmpty(this.webpackConfig.output)) { | ||
const outputPath = path.join(this.serverless.config.servicePath, '.webpack'); | ||
const outputFilename = path.basename( | ||
_.isArray(this.webpackConfig.entry) | ||
&& this.webpackConfig.entry[this.webpackConfig.entry.length - 1] | ||
|| this.webpackConfig.entry | ||
|| 'handler.js' | ||
); | ||
this.webpackConfig.output = { | ||
libraryTarget: 'commonjs', | ||
path: outputPath, | ||
filename: outputFilename, | ||
filename: '[name].js', | ||
}; | ||
@@ -129,5 +124,51 @@ } | ||
fse.removeSync(this.webpackConfig.output.path); | ||
this.webpackOutputPath = this.webpackConfig.output.path; | ||
// In case of individual packaging we have to create a separate config for each function | ||
if (_.has(this.serverless, 'service.package') && this.serverless.service.package.individually) { | ||
this.options.verbose && this.serverless.cli.log('Using multi-compile (individual packaging)'); | ||
this.multiCompile = true; | ||
// Lookup associated Serverless functions | ||
const allEntryFunctions = _.map( | ||
this.serverless.service.getAllFunctions(), | ||
funcName => { | ||
const func = this.serverless.service.getFunction(funcName); | ||
const handler = func.handler; | ||
const handlerFile = path.relative('.', /(.*)\..*?$/.exec(handler)[1]); | ||
return { | ||
handlerFile, | ||
funcName, | ||
func | ||
}; | ||
} | ||
); | ||
this.entryFunctions = _.map(this.webpackConfig.entry, (value, key) => { | ||
const entry = path.relative('.', value); | ||
const entryFile = _.replace(entry, new RegExp(`${path.extname(entry)}$`), ''); | ||
const entryFunc = _.find(allEntryFunctions, [ 'handlerFile', entryFile ]) || {}; | ||
entryFunc.entry = { | ||
key, | ||
value | ||
}; | ||
return entryFunc; | ||
}); | ||
this.webpackConfig = _.map(this.entryFunctions, entryFunc => { | ||
const config = _.cloneDeep(this.webpackConfig); | ||
config.entry = { | ||
[entryFunc.entry.key]: entryFunc.entry.value | ||
}; | ||
const compileName = entryFunc.funcName || _.camelCase(entryFunc.entry.key); | ||
config.output.path = path.join(config.output.path, compileName); | ||
return config; | ||
}); | ||
} else { | ||
this.webpackConfig.output.path = path.join(this.webpackConfig.output.path, 'service'); | ||
} | ||
return BbPromise.resolve(); | ||
}, | ||
}; |
{ | ||
"name": "serverless-webpack", | ||
"version": "2.2.0", | ||
"version": "3.0.0-rc.1", | ||
"description": "Serverless plugin to bundle your javascript with Webpack", | ||
@@ -30,14 +30,20 @@ "main": "index.js", | ||
"dependencies": { | ||
"archiver": "^2.0.0", | ||
"bluebird": "^3.4.0", | ||
"body-parser": "^1.15.2", | ||
"express": "^4.14.0", | ||
"fs-extra": "^0.26.7", | ||
"glob": "^7.1.2", | ||
"is-builtin-module": "^1.0.0", | ||
"lodash": "^4.17.4", | ||
"npm-programmatic": "0.0.5", | ||
"npm-programmatic": "^0.0.7", | ||
"semver": "^5.4.1", | ||
"ts-node": "^3.2.0" | ||
}, | ||
"devDependencies": { | ||
"babel-eslint": "^7.2.3", | ||
"chai": "^4.1.0", | ||
"chai-as-promised": "^7.1.1", | ||
"eslint": "^4.3.0", | ||
"eslint-plugin-import": "^2.7.0", | ||
"eslint-plugin-lodash": "^2.4.4", | ||
"eslint-plugin-promise": "^3.5.0", | ||
"istanbul": "^0.4.5", | ||
@@ -44,0 +50,0 @@ "mocha": "^3.4.2", |
195
README.md
@@ -12,7 +12,26 @@ # Serverless Webpack | ||
This plugin is for you if you want to use the latest Javascript version with [Babel][link-babel]; | ||
use custom [resource loaders][link-webpack-loaders]; | ||
try your lambda functions locally and much more! | ||
use custom [resource loaders][link-webpack-loaders], optimize your packaged functions individually | ||
and much more! | ||
> **BREAKING CHANGE IN v2**: `webpack` must now be installed alongside `serverless-webpack` as a peer dependency. This allows more control over which version of Webpack to run. | ||
## Highlights | ||
* Configuration possibilities range from zero-config to fully customizable | ||
* Support of `serverless package`, `serverless deploy` and `serverless deploy function` | ||
* Support of `serverless invoke local` and `serverless invoke local --watch` | ||
* Integrates with [`serverless-offline`][link-serverless-offline] to simulate local API Gateway endpoints | ||
* When enabled in your service configuration, functions are packaged and compiled | ||
individually, resulting in smaller Lambda packages that contain only the code and | ||
dependencies needed to run the function. This allows the plugin to fully utilize | ||
WebPack's [Tree-Shaking][link-webpack-tree] optimization. | ||
## New in V3 | ||
* Support for individual packaging and optimization | ||
* Integrate with `serverless invoke local` (including watch mode) | ||
* Stabilized package handling | ||
* Improved compatibility with other plugins | ||
* Updated examples | ||
For the complete release notes see the end of this document. | ||
## Install | ||
@@ -59,2 +78,4 @@ | ||
to make the configuration easier and to build fully dynamic configurations. | ||
This is the preferred way to configure webpack - the plugin will take care of | ||
as much of the configuration (and subsequent changes in your services) as it can. | ||
@@ -159,2 +180,3 @@ #### Automatic entry resolution | ||
All modules stated in `externals` will be excluded from bundled files. If an excluded module | ||
@@ -177,2 +199,39 @@ is stated as `dependencies` in `package.json`, it will be packed into the Serverless | ||
#### Service level packaging | ||
If you do not enable individual packaging in your service (serverless.yml), the | ||
plugin creates one ZIP file for all functions (the service package) that includes | ||
all node modules used in the service. This is the fastest packaging, but not the | ||
optimal one, as node modules are always packaged, that are not needed by some | ||
functions. | ||
#### Optimization / Individual packaging per function | ||
A better way to do the packaging, is to enable individual packaging in your | ||
service: | ||
```yaml | ||
# serverless.yml | ||
... | ||
package: | ||
individually: true | ||
... | ||
``` | ||
This will switch the plugin to per function packaging which makes use of the multi-compiler | ||
feature of Webpack. That means, that Webpack compiles **and optimizes** each | ||
function individually, removing unnecessary imports and reducing code sizes | ||
significantly. Tree-Shaking only makes sense with this approach. | ||
Now the needed external node modules are also detected by Webpack per function | ||
and the plugin only packages the needed ones into the function artifacts. As a | ||
result, the deployed artifacts are smaller, depending on the functions and | ||
cold-start times (to install the functions into the cloud at runtime) are reduced | ||
too. | ||
The individual packaging should be combined with the _automatic entry resolution_ (see above). | ||
The individual packaging needs more time at the packaging phase, but you'll | ||
get that paid back twice at runtime. | ||
## Usage | ||
@@ -188,2 +247,39 @@ | ||
### Run a function locally | ||
The plugin fully integrates with `serverless invoke local`. To run your bundled functions | ||
locally you can: | ||
```bash | ||
$ serverless invoke local --function <function-name> | ||
``` | ||
All options that are supported by invoke local can be used as usual: | ||
- `--function` or `-f` (required) is the name of the function to run | ||
- `--path` or `-p` (optional) is a JSON file path used as the function input event | ||
- `--data` or `-d` (optional) inline JSON data used as the function input event | ||
> :exclamation: The old `webpack invoke` command has been disabled. | ||
### Run a function locally on source changes | ||
Or to run a function every time the source files change use the `--watch` option | ||
together with `serverless invoke local`: | ||
```bash | ||
$ serverless invoke local --function <function-name> --path event.json --watch | ||
``` | ||
Everytime the sources are changed, the function will be executed again with the | ||
changed sources. The command will watch until the process is terminated. | ||
All options that are supported by invoke local can be used as usual: | ||
- `--function` or `-f` (required) is the name of the function to run | ||
- `--path` or `-p` (optional) is a JSON file path used as the function input event | ||
- `--data` or `-d` (optional) inline JSON data used as the function input event | ||
> :exclamation: The old `webpack watch` command has been disabled. | ||
### Usage with serverless-offline | ||
@@ -234,28 +330,2 @@ | ||
### Run a function locally | ||
To run your bundled functions locally you can: | ||
```bash | ||
$ serverless webpack invoke --function <function-name> | ||
``` | ||
Options are: | ||
- `--function` or `-f` (required) is the name of the function to run | ||
- `--path` or `-p` (optional) is a JSON file path used as the function input event | ||
### Run a function locally on source changes | ||
Or to run a function every time the source files change use `watch`: | ||
```bash | ||
$ serverless webpack watch --function <function-name> --path event.json | ||
``` | ||
Options are: | ||
- `--function` or `-f` (required) is the name of the function to run | ||
- `--path` or `-p` (optional) is a JSON file path used as the function input event | ||
### Bundle with webpack | ||
@@ -275,32 +345,5 @@ | ||
_The integrated simulation functionality will be removed in version 3 in favor of | ||
using `serverless-offline` (see [#135][link-135]) which already does the job | ||
perfectly and fully integrates with `serverless-webpack`. | ||
Please switch to `serverless-offline` if you do not use it already._ | ||
:exclamation: The serve command has been removed. See above how to achieve the | ||
same functionality with the [`serverless-offline`][link-serverless-offline] plugin. | ||
To start a local server that will act like API Gateway, use the following command. | ||
Your code will be reloaded upon change so that every request to your local server | ||
will serve the latest code. | ||
```bash | ||
$ serverless webpack serve | ||
``` | ||
Options are: | ||
- `--port` or `-p` (optional) The local server port. Defaults to `8000` | ||
The `serve` command will automatically look for the local `serverless.yml` and serve | ||
all the `http` events. For example this configuration will generate a GET endpoint: | ||
```yaml | ||
functions: | ||
hello: | ||
handler: handler.hello | ||
events: | ||
- http: | ||
method: get | ||
path: hello | ||
``` | ||
## Example with Babel | ||
@@ -312,3 +355,3 @@ | ||
- `npm install` to install dependencies | ||
- `serverless webpack run -f hello` to run the example function | ||
- `serverless invoke local -f hello` to run the example function | ||
@@ -319,11 +362,22 @@ ## Provider Support | ||
| | AWS Lambda | Apache OpenWhisk | Azure Functions | Google Cloud Functions | | ||
|----------------|------------|------------------|-----------------|------------------------| | ||
| webpack | ✔︎ | ✔︎ | ⁇ | ⁇ | | ||
| webpack invoke | ✔︎ | ✘ | ⁇ | ⁇ | | ||
| webpack watch | ✔︎ | ✔︎ | ⁇ | ⁇ | | ||
| webpack serve | ✔︎ | ✘ | ⁇ | ⁇ | | ||
| | AWS Lambda | Apache OpenWhisk | Azure Functions | Google Cloud Functions | | ||
|-----------------------|------------|------------------|-----------------|------------------------| | ||
| webpack | ✔︎ | ✔︎ | ⁇ | ⁇ | | ||
| invoke local | ✔︎ | ⁇ | ⁇ | ⁇ | | ||
| invoke local --watch | ✔︎ | ⁇ | ⁇ | ⁇ | | ||
## Release Notes | ||
* 3.0.0 | ||
* Integrate with `serverless invoke local` [#151][link-151] | ||
* Support watch mode with `serverless invoke local --watch` | ||
* Stabilized and improved the bundling of node modules [#116][link-116], [#117][link-117] | ||
* Improved interoperability with Serverless and 3rd party plugins [#173][link-173] | ||
* Support individual packaging of the functions in a service [#120][link-120] | ||
* Allow setting stdio max buffers for NPM operations [#185][link-185] | ||
* Support bundling of node modules via node-externals whitelist [#186][link-186] | ||
* Removed the `webpack serve` command in favor of [`serverless-offline`][link-serverless-offline] [#152][link-152] | ||
* Updated examples [#179][link-179] | ||
* Added missing unit tests to improve code stability | ||
* 2.2.0 | ||
@@ -362,2 +416,3 @@ * Allow full dynamic configurations [#158][link-158] | ||
[link-webpack-libtarget]: https://webpack.github.io/docs/configuration.html#output-librarytarget | ||
[link-webpack-tree]: https://webpack.js.org/guides/tree-shaking/ | ||
[link-webpack-externals]: https://webpack.github.io/docs/configuration.html#externals | ||
@@ -389,1 +444,11 @@ [link-examples]: ./examples | ||
[link-165]: https://github.com/elastic-coders/serverless-webpack/issues/165 | ||
[link-116]: https://github.com/elastic-coders/serverless-webpack/issues/116 | ||
[link-117]: https://github.com/elastic-coders/serverless-webpack/issues/117 | ||
[link-120]: https://github.com/elastic-coders/serverless-webpack/issues/120 | ||
[link-151]: https://github.com/elastic-coders/serverless-webpack/issues/151 | ||
[link-152]: https://github.com/elastic-coders/serverless-webpack/issues/152 | ||
[link-173]: https://github.com/elastic-coders/serverless-webpack/issues/173 | ||
[link-179]: https://github.com/elastic-coders/serverless-webpack/pull/179 | ||
[link-185]: https://github.com/elastic-coders/serverless-webpack/pull/185 | ||
[link-186]: https://github.com/elastic-coders/serverless-webpack/pull/186 |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
48574
18
444
7
10
13
682
1
2
+ Addedarchiver@^2.0.0
+ Addedis-builtin-module@^1.0.0
+ Addedsemver@^5.4.1
+ Addedarchiver@2.1.1(transitive)
+ Addedarchiver-utils@1.3.0(transitive)
+ Addedasync@2.6.4(transitive)
+ Addedbase64-js@1.5.1(transitive)
+ Addedbl@1.2.3(transitive)
+ Addedbuffer@5.7.1(transitive)
+ Addedbuffer-alloc@1.2.0(transitive)
+ Addedbuffer-alloc-unsafe@1.1.0(transitive)
+ Addedbuffer-crc32@0.2.13(transitive)
+ Addedbuffer-fill@1.0.0(transitive)
+ Addedbuiltin-modules@1.1.1(transitive)
+ Addedcompress-commons@1.2.2(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addedcrc@3.8.0(transitive)
+ Addedcrc32-stream@2.0.0(transitive)
+ Addedend-of-stream@1.4.4(transitive)
+ Addedfs-constants@1.0.0(transitive)
+ Addedieee754@1.2.1(transitive)
+ Addedis-builtin-module@1.0.0(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedlazystream@1.0.1(transitive)
+ Addednormalize-path@2.1.1(transitive)
+ Addednpm-programmatic@0.0.7(transitive)
+ Addedprocess-nextick-args@2.0.1(transitive)
+ Addedreadable-stream@2.3.8(transitive)
+ Addedremove-trailing-separator@1.1.0(transitive)
+ Addedsafe-buffer@5.1.2(transitive)
+ Addedsemver@5.7.2(transitive)
+ Addedstring_decoder@1.1.1(transitive)
+ Addedtar-stream@1.6.2(transitive)
+ Addedto-buffer@1.1.1(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
+ Addedxtend@4.0.2(transitive)
+ Addedzip-stream@1.2.0(transitive)
- Removedbody-parser@^1.15.2
- Removedexpress@^4.14.0
- Removedaccepts@1.3.8(transitive)
- Removedarray-flatten@1.1.1(transitive)
- Removedbody-parser@1.20.3(transitive)
- Removedbytes@3.1.2(transitive)
- Removedcall-bind-apply-helpers@1.0.2(transitive)
- Removedcall-bound@1.0.4(transitive)
- Removedcontent-disposition@0.5.4(transitive)
- Removedcontent-type@1.0.5(transitive)
- Removedcookie@0.7.1(transitive)
- Removedcookie-signature@1.0.6(transitive)
- Removeddebug@2.6.9(transitive)
- Removeddepd@2.0.0(transitive)
- Removeddestroy@1.2.0(transitive)
- Removeddunder-proto@1.0.1(transitive)
- Removedee-first@1.1.1(transitive)
- Removedencodeurl@1.0.22.0.0(transitive)
- Removedes-define-property@1.0.1(transitive)
- Removedes-errors@1.3.0(transitive)
- Removedes-object-atoms@1.1.1(transitive)
- Removedescape-html@1.0.3(transitive)
- Removedetag@1.8.1(transitive)
- Removedexpress@4.21.2(transitive)
- Removedfinalhandler@1.3.1(transitive)
- Removedforwarded@0.2.0(transitive)
- Removedfresh@0.5.2(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedget-intrinsic@1.3.0(transitive)
- Removedget-proto@1.0.1(transitive)
- Removedgopd@1.2.0(transitive)
- Removedhas-symbols@1.1.0(transitive)
- Removedhasown@2.0.2(transitive)
- Removedhttp-errors@2.0.0(transitive)
- Removediconv-lite@0.4.24(transitive)
- Removedipaddr.js@1.9.1(transitive)
- Removedmath-intrinsics@1.1.0(transitive)
- Removedmedia-typer@0.3.0(transitive)
- Removedmerge-descriptors@1.0.3(transitive)
- Removedmethods@1.1.2(transitive)
- Removedmime@1.6.0(transitive)
- Removedms@2.0.02.1.3(transitive)
- Removednegotiator@0.6.3(transitive)
- Removednpm-programmatic@0.0.5(transitive)
- Removedobject-inspect@1.13.4(transitive)
- Removedon-finished@2.4.1(transitive)
- Removedparseurl@1.3.3(transitive)
- Removedpath-to-regexp@0.1.12(transitive)
- Removedproxy-addr@2.0.7(transitive)
- Removedqs@6.13.0(transitive)
- Removedrange-parser@1.2.1(transitive)
- Removedraw-body@2.5.2(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsend@0.19.0(transitive)
- Removedserve-static@1.16.2(transitive)
- Removedsetprototypeof@1.2.0(transitive)
- Removedside-channel@1.1.0(transitive)
- Removedside-channel-list@1.0.0(transitive)
- Removedside-channel-map@1.0.1(transitive)
- Removedside-channel-weakmap@1.0.2(transitive)
- Removedstatuses@2.0.1(transitive)
- Removedtoidentifier@1.0.1(transitive)
- Removedtype-is@1.6.18(transitive)
- Removedunpipe@1.0.0(transitive)
- Removedutils-merge@1.0.1(transitive)
- Removedvary@1.1.2(transitive)
Updatednpm-programmatic@^0.0.7