Skip to content

Commit 7ebe3d8

Browse files
committed
fix: adapt to serverless artifact validation introduced in v2.51.1
BREAKING CHANGE: bundling flow modified, may affect older versions
1 parent 67288f2 commit 7ebe3d8

6 files changed

Lines changed: 102 additions & 536 deletions

File tree

README.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ Serverless Browserifier Plugin
77
[![NPM downloads][downloads-badge]][npm-url]
88
[![standardjs][standardjs-badge]][standardjs-url]
99

10-
> A [Serverless](https://serverless.com) v1 plugin that uses [`browserify`][browserify-url] to bundle your Node.js Lambda functions.
10+
> A [Serverless](https://serverless.com) v1 and v2 plugin that uses [`browserify`][browserify-url] to bundle your Node.js Lambda functions.
1111
12+
1. [Supported Commands](#supported-commands)
1213
1. [Motivation](#motivation)
1314
1. [Installation](#installation)
1415
1. [Basic Setup](#basic-setup)
@@ -18,6 +19,16 @@ Serverless Browserifier Plugin
1819
1. [License](#license)
1920

2021

22+
Supported Commands
23+
------------------
24+
25+
- `serverless package`
26+
- `serverless deploy`
27+
- `serverless deploy function`
28+
- `serverless invoke local`
29+
+ the plugin will automatically build and use your bundle to execute the function locally; to avoid it, you can use the `--no-build` option, which will then use your handler file directly
30+
31+
2132
Motivation
2233
----------
2334

@@ -80,14 +91,17 @@ The base config can be overridden on a function-by-function basis. Again, `cust
8091
custom:
8192
browserify:
8293
# any option defined in https://github.com/substack/node-browserify#browserifyfiles--opts
94+
extensions:
95+
- .js # default
8396
8497
functions:
8598
usersGet:
8699
description: Get users
87100
handler: users/index.handler
88101
browserify:
102+
# any option defined in https://github.com/substack/node-browserify#browserifyfiles--opts
89103
noParse:
90-
- ./someBig.json #browserify can't optimize json, will take long time to parse for nothing
104+
- ./someBig.json # browserify can't optimize json, will take long time to parse for nothing
91105
```
92106

93107
If you find a package that is not supported or does not behave well with browserify, you can still use function level `package.include` to include extra modules and files to your package. That said, you are encouraged to verify if you specific case can be dealt with by leveraging all available [browserify options][browserify-options] in your `serverless.yml` custom `browserify` section.
@@ -120,7 +134,7 @@ When this plugin is enabled, and `package.individually` is `true`, running `serv
120134
If you want to see more information about the process, simply set envvar `SLS_DEBUG=*` for full serverless debug output, or `SLS_BROWSERIFIER_DEBUG=*` for plugin only debug messages. Example:
121135

122136
```
123-
$ export SLS_DEBUG=*
137+
$ export SLS_BROWSERIFIER_DEBUG=*
124138
$ sls deploy function -v -f usersGet
125139
```
126140

@@ -174,7 +188,7 @@ const S3 = require('aws-sdk/clients/s3')
174188
const s3 = new S3()
175189
```
176190

177-
#### Ignore AWS SDK!
191+
#### Ignore AWS SDK v2!
178192

179193
Although you can use discrete clients (see item above), AWS Lambda service always bundles up the latest SDK version in its Lambda container image. That means that, even if you don't add AWS SDK to your bundle, it will still be available in runtime.
180194

@@ -194,7 +208,7 @@ To help you out, here's a script you can use to hide `aws-sdk` and all its clien
194208
# serverless.yml
195209
196210
custom:
197-
browserify: browserify: ${file(./custom.browserify.js)}
211+
browserify: ${file(./custom.browserify.js)}
198212
```
199213

200214
```js
@@ -230,9 +244,9 @@ __What about uglification? And babel?__
230244

231245
You should be able to use [`uglify-es`][uglify-url] through [`uglifyify`][uglifyify-url]. For babel usage, [`babelify`][babelify-url] should do the trick. Refer back to [_Using browserify plugins_](#using-browserify-pluginstransforms) section to set it up.
232246

233-
__Avoid mixing this plugin with other plugins that modify serverless' packaging behaviour!__
247+
__This other plugin I use is not playing ball with `serverless-plugin-browserifier`! What's up?__
234248

235-
This plugin _hijacks_ the normal serverless packaging process, so it will probably conflict with other plugins that use similar mechanisms.
249+
This plugin _hijacks_ the normal serverless packaging process, so it will probably conflict with other plugins that use similar mechanisms. Please avoid mixing this plugin with other plugins that modify serverless' packaging behaviour.
236250

237251

238252
Useful Information

lib/bundle.js

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
'use strict'
22

3-
const Promise = require('bluebird')
3+
const path = require('path')
4+
const Bb = require('bluebird')
45

56
const archiver = require('archiver')
67
const browserify = require('browserify')
78
const filesize = require('filesize')
89
const globby = require('globby')
9-
const path = require('path')
1010
const semver = require('semver')
1111

12+
const errors = require('./errors')
1213
const fs = require('./fs')
1314

1415
/// //////////////////////// exports && main functions
@@ -19,29 +20,33 @@ module.exports = {
1920
}
2021

2122
function bundle (functionName) {
22-
const data = this._b.functionConfigCache[functionName]
23-
if (!data) {
24-
return Promise.resolve()
23+
if (this._b.debugOn) {
24+
this._s.cli.log(`Browserifier: Bundling "${functionName}"...`)
2525
}
26-
return Promise.bind(this)
27-
.return(data)
28-
.then(clean)
26+
return Bb.bind(this)
27+
.return(functionName)
28+
.then(getConfigFromCache)
2929
.then(prepareIncludes)
3030
.then(runBrowserify)
31-
.then(zipIt)
32-
.then(clean)
31+
.then(data => {
32+
if (this._b.localInvoke === true) {
33+
return data
34+
}
35+
return Bb.bind(this).return(data).then(zipIt).then(clean)
36+
})
3337
}
3438

3539
function bootstrap (functionName) {
3640
if (this._b.debugOn) {
37-
this.S.cli.log(`Browserifier: Preparing "${functionName}"...`)
41+
this._s.cli.log(`Browserifier: Preparing "${functionName}"...`)
3842
}
39-
return Promise.bind(this)
43+
return Bb.bind(this)
4044
.return(functionName)
4145
.then(prepareInitialData)
4246
.then(this._validateFunction)
4347
.then(cacheConfig)
4448
.then(fixServerlessConfig)
49+
.then(clean)
4550
}
4651

4752
/// //////////////////////// support functions
@@ -50,15 +55,23 @@ function prepareInitialData (functionName) {
5055
return {
5156
functionName,
5257
outputFolder: path.join(this._b.servicePath, functionName),
53-
functionObject: this.S.service.getFunction(functionName),
58+
functionObject: this._s.service.getFunction(functionName),
5459
functionBrowserifyConfig: this._getFunctionConfig(functionName),
5560
outputBundle: path.relative(
56-
this.S.config.servicePath,
61+
this._s.config.servicePath,
5762
path.join(this._b.servicePath, `${functionName}.zip`)
5863
)
5964
}
6065
}
6166

67+
function getConfigFromCache (functionName) {
68+
const data = this._b.functionConfigCache[functionName]
69+
if (!data) {
70+
throw new errors.SkipFunctionError(functionName + ' could not be found in config cache')
71+
}
72+
return data
73+
}
74+
6275
function cacheConfig (data) {
6376
if (data) {
6477
this._b.functionConfigCache[data.functionName] = data
@@ -68,53 +81,59 @@ function cacheConfig (data) {
6881

6982
function prepareIncludes (data) {
7083
const includeFiles = globby.sync(data.functionBrowserifyConfig.include, {
71-
cwd: this.S.config.servicePath,
84+
cwd: this._s.config.servicePath,
7285
dot: true,
7386
silent: true,
7487
follow: true
7588
})
7689
if (includeFiles && includeFiles.length) {
7790
if (this._b.debugOn) {
78-
this.S.cli.log('Browserifier: Copying includes: ' + includeFiles)
91+
this._s.cli.log('Browserifier: Copying includes: ' + includeFiles)
7992
}
8093
const copyFile = file => {
81-
fs.copySync(path.join(this.S.config.servicePath, file), path.join(data.outputFolder, file))
94+
fs.copySync(path.join(this._s.config.servicePath, file), path.join(data.outputFolder, file))
8295
}
83-
return Promise.each(includeFiles, copyFile).return(data)
96+
return Bb.each(includeFiles, copyFile).return(data)
8497
}
8598
return data
8699
}
87100

88101
function runBrowserify (data) {
89102
if (this._b.debugOn) {
90-
this.S.cli.log(`Browserifier: Browserifying ${data.functionName}...`)
103+
this._s.cli.log(`Browserifier: Browserifying ${data.functionName}...`)
91104
}
92105
const cfg = data.functionBrowserifyConfig
93106
const b = browserify(cfg)
94107
cfg.exclude.forEach(file => b.exclude(file))
95108
cfg.ignore.forEach(file => b.ignore(file))
96109
cfg.external.forEach(file => b.external(file))
97-
return Promise.fromCallback(cb => b.bundle(cb))
110+
return Bb.fromCallback(cb => b.bundle(cb))
98111
.then(bundleBuffer => {
99112
if (this._b.debugOn) {
100-
this.S.cli.log(`Browserifier: Writing browserified bundle to ${data.outputFolder}...`)
113+
this._s.cli.log(`Browserifier: Writing browserified bundle to ${data.outputFolder}...`)
114+
}
115+
const [handlerPath, handlerFunction] = data.functionObject.handler.split('.')
116+
let filePath = path.join(data.outputFolder, handlerPath)
117+
if (this._b.localInvoke === true) {
118+
const newHandlerPath = path.relative(this._s.config.servicePath, filePath)
119+
data.functionObject.handler = newHandlerPath + '.' + handlerFunction
120+
this._s.cli.log(`Browserifier: Function ${data.functionName} bundle generated...`)
101121
}
102-
let handlerPath = data.functionObject.handler.split('.')[0] + '.js'
103-
handlerPath = path.join(data.outputFolder, handlerPath)
104-
this.S.utils.writeFileDir(handlerPath)
105-
return Promise.fromCallback(cb => fs.writeFile(handlerPath, bundleBuffer, cb))
122+
filePath += '.js'
123+
this._s.utils.writeFileDir(filePath)
124+
return Bb.fromCallback(cb => fs.writeFile(filePath, bundleBuffer, cb))
106125
})
107126
.tap(() => {
108127
if (this._b.debugOn) {
109-
this.S.cli.log(`Browserifier: Browserified output dumped to ${data.outputFolder}...`)
128+
this._s.cli.log(`Browserifier: Browserified output dumped to ${data.outputFolder}...`)
110129
}
111130
})
112131
.return(data)
113132
}
114133

115134
function zipIt (data) {
116135
if (this._b.debugOn) {
117-
this.S.cli.log(`Browserifier: Zipping ${data.outputFolder} to ${data.outputBundle}...`)
136+
this._s.cli.log(`Browserifier: Zipping ${data.outputFolder} to ${data.outputBundle}...`)
118137
}
119138
const handleStream = (resolve, reject) => {
120139
const output = fs.getNewFileStream(data.outputBundle)
@@ -127,8 +146,8 @@ function zipIt (data) {
127146
zip.finalize()
128147
})
129148
}
130-
return new Promise(handleStream).then(sizeInBytes => {
131-
this.S.cli.log(`Browserifier: Created ${data.functionName}.zip (${filesize(sizeInBytes)})...`)
149+
return new Bb(handleStream).then(sizeInBytes => {
150+
this._s.cli.log(`Browserifier: Created ${data.functionName}.zip (${filesize(sizeInBytes)})...`)
132151
return data
133152
})
134153
}
@@ -141,7 +160,7 @@ function clean (data) {
141160
}
142161

143162
function fixServerlessConfig (data) {
144-
if (semver.lt(this.S.getVersion(), '1.18.0')) {
163+
if (semver.lt(this._s.getVersion(), '1.18.0')) {
145164
data.functionObject.artifact = data.outputBundle
146165
data.functionObject.package = Object.assign({}, data.functionObject.package, { disable: true })
147166
} else {

lib/configure.js

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,27 @@ module.exports = {
1010
*/
1111
_computeGlobalConfig () {
1212
if (this._b.debugOn) {
13-
this.S.cli.log('Browserifier: Computing global config...')
13+
this._s.cli.log('Browserifier: Computing global config...')
1414
}
15-
const globalCustom = get(this.S, 'service.custom.browserify', {})
15+
const globalCustom = get(this._s, 'service.custom.browserify', {})
1616
const globalDefault = this._getBrowserifyDefaultConfig()
1717
this._b.globalBrowserifyConfig = Object.assign({}, globalDefault, globalCustom)
18-
if (this.S.service.package) {
18+
if (this._s.service.package) {
1919
// Merge together package.exclude and custom.browserify.exclude
20-
if (this.S.service.package.exclude && this.S.service.package.exclude.length) {
20+
if (this._s.service.package.exclude && this._s.service.package.exclude.length) {
2121
this._b.globalBrowserifyConfig.exclude = union(
22-
this.S.service.package.exclude,
22+
this._s.service.package.exclude,
2323
this._b.globalBrowserifyConfig.exclude
2424
)
2525
}
2626
// Save service package.include
27-
if (this.S.service.package.include && this.S.service.package.include.length) {
28-
this._b.globalBrowserifyConfig.include = this.S.service.package.include
27+
if (this._s.service.package.include && this._s.service.package.include.length) {
28+
this._b.globalBrowserifyConfig.include = this._s.service.package.include
2929
}
3030
}
3131
if (this._b.debugOn) {
3232
const computed = JSON.stringify(this._b.globalBrowserifyConfig)
33-
this.S.cli.log('Browserifier: Computed globalBrowserifyConfig: ' + computed)
33+
this._s.cli.log('Browserifier: Computed globalBrowserifyConfig: ' + computed)
3434
}
3535
},
3636

@@ -41,9 +41,9 @@ module.exports = {
4141
_getBrowserifyDefaultConfig () {
4242
const version = this._getBrowserifyVersion()
4343
if (semver.lt(version, '13.3.0')) {
44-
throw new this.S.classes.Error("Minimal 'browserify' version supported is v13.3.0.", 'skip')
44+
throw new this._s.classes.Error("Minimal 'browserify' version supported is v13.3.0.", 'skip')
4545
}
46-
this.S.cli.log('Browserifier: Using browserify@' + version)
46+
this._s.cli.log('Browserifier: Using browserify@' + version)
4747
// Props disable, exclude, include, external and ignore are not browserify
4848
// options, but will be used for custom plugin settings if defined in yml
4949
const config = {
@@ -52,31 +52,32 @@ module.exports = {
5252
include: [],
5353
external: [],
5454
ignore: [],
55-
basedir: this.S.config.servicePath,
55+
basedir: this._s.config.servicePath,
5656
entries: [],
5757
bare: true,
5858
standalone: 'lambda',
59+
extensions: ['.js'],
5960
ignoreMissing: true, // Do not fail on missing optional dependencies
6061
detectGlobals: true, // We don't care if its slower, we want more mods to work
6162
debug: this._b.debugOn
6263
}
6364
if (semver.gte(version, '16.1.0')) {
6465
if (this._b.debugOn) {
65-
this.S.cli.log('Browserifier: Using browserify "node" option...')
66+
this._s.cli.log('Browserifier: Using browserify "node" option...')
6667
}
6768
config.node = true
6869
} else {
6970
if (this._b.debugOn) {
70-
this.S.cli.log('Browserifier: Using browserify "legacy" options...')
71+
this._s.cli.log('Browserifier: Using browserify "legacy" options...')
7172
}
7273
Object.assign(config, {
7374
browserField: false, // Setup for node app (copy logic of --node in bin/args.js)
7475
builtins: false,
7576
commondir: false,
7677
insertGlobalVars: {
77-
process: undefined,
78-
global: undefined,
79-
Buffer: undefined,
78+
'process': undefined,
79+
'global': undefined,
80+
'Buffer': undefined,
8081
'Buffer.isBuffer': undefined
8182
}
8283
})
@@ -93,7 +94,7 @@ module.exports = {
9394
const pkg = require('browserify/package.json')
9495
return pkg.version
9596
} catch (err) {
96-
throw new this.S.classes.Error('Required peer dependency "browserify" is not available.')
97+
throw new this._s.classes.Error('Required peer dependency "browserify" is not available.')
9798
}
9899
},
99100

@@ -104,18 +105,18 @@ module.exports = {
104105
* @returns {*}
105106
*/
106107
_getFunctionConfig (functionName) {
107-
const functionObject = this.S.service.getFunction(functionName)
108+
const functionObject = this._s.service.getFunction(functionName)
108109
const functionBrowserifyConfig = Object.assign(
109110
{},
110111
this._b.globalBrowserifyConfig,
111112
functionObject.browserify || {}
112113
)
113114
if (this._b.debugOn) {
114115
const computed = JSON.stringify(functionObject)
115-
this.S.cli.log(`Browserifier: functionObject for ${functionName}: ${computed}`)
116+
this._s.cli.log(`Browserifier: functionObject for ${functionName}: ${computed}`)
116117
}
117118
if (!functionBrowserifyConfig.entries.length) {
118-
const bundleEntryPt = functionObject.handler.split('.')[0] + '.js'
119+
const bundleEntryPt = functionObject.handler.split('.')[0]
119120
functionBrowserifyConfig.entries = [bundleEntryPt]
120121
}
121122
if (functionObject.package) {
@@ -136,7 +137,7 @@ module.exports = {
136137
}
137138
if (this._b.debugOn) {
138139
const computed = JSON.stringify(functionBrowserifyConfig)
139-
this.S.cli.log('Browserifier: Computed function BrowserifierConfig: ' + computed)
140+
this._s.cli.log('Browserifier: Computed function BrowserifierConfig: ' + computed)
140141
}
141142
return functionBrowserifyConfig
142143
}

0 commit comments

Comments
 (0)