Skip to content

Commit 8465e10

Browse files
authored
Merge pull request #18 from digitalmaas/feature/improvements
Feature/improvements
2 parents 24f58c6 + b61797e commit 8465e10

File tree

7 files changed

+965
-1371
lines changed

7 files changed

+965
-1371
lines changed

README.md

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ Serverless Browserifier Plugin
99

1010
> A [Serverless](https://serverless.com) v1 plugin that uses [`browserify`][browserify-url] to bundle your Node.js Lambda functions.
1111
12+
1. [Motivation](#motivation)
13+
1. [Installation](#installation)
14+
1. [Basic Setup](#basic-setup)
15+
1. [Advanced Configuration](#advanced-configuration)
16+
1. [FAQ](#faq)
17+
1. [Useful Information](#useful-information)
18+
1. [License](#license)
19+
20+
1221
Motivation
1322
----------
1423

@@ -41,6 +50,9 @@ From your target serverless project, run:
4150
npm install serverless-plugin-browserifier --save-dev
4251
```
4352

53+
Basic Setup
54+
-----------
55+
4456
Add the plugin to your `serverless.yml`:
4557

4658
```yaml
@@ -52,11 +64,13 @@ package:
5264
5365
The `package.individually` setting must be set -- either on global or function level -- to allow minimal bundle size based on each lambda's entrypoint.
5466

67+
You're all set! Use your normal serverless commands to package and deploy.
5568

56-
Configuration
57-
-------------
5869

59-
For most use cases you should **NOT** need to do any configuration. You can, however, introduce custom configuration.
70+
Advanced Configuration
71+
----------------------
72+
73+
For most use cases you should **NOT** need to do any extra configuration. That said, the ability is present if you need it.
6074

6175
The base config for browserify is read from the `custom.browserify` section of `serverless.yml`. All [browserify options][browserify-options] are supported (most are auto configured by this plugin). This plugin adds one special option `disable` which if `true` will bypass this plugin.
6276

@@ -99,22 +113,21 @@ functions:
99113
```
100114

101115

102-
Usage
103-
-----
116+
### Debugging
104117

105118
When this plugin is enabled, and `package.individually` is `true`, running `serverless deploy` and `serverless deploy -f <funcName>` will automatically browserify your Node.js lambda code.
106119

107-
If you want to see more information about the process, simply set `SLS_DEBUG=*`. Example:
120+
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:
121+
108122
```
109123
$ export SLS_DEBUG=*
110124
$ sls deploy function -v -f usersGet
111125
```
112126

113-
You can also verify your bundles by simply using `sls package`, which bundles everything up but does not deploy.
127+
You may also verify your bundles by simply using `sls package`, which bundles everything up but does not deploy.
114128

115129

116-
Using browserify plugins/transforms
117-
-----------------------------------
130+
### Using browserify plugins/transforms
118131

119132
If you want to use browserify plugins, you can easily do that by using the global browserify options. As the plugin merely passes that up to browserify, as if it is calling the main [`browserify`][browserify-options] function, you can use it to add any transformations you want.
120133

@@ -146,10 +159,9 @@ custom:
146159
For an in-depth example, please check [this issue](https://github.com/digitalmaas/serverless-plugin-browserifier/issues/8).
147160

148161

149-
Best practices
150-
--------------
162+
### Best practices
151163

152-
__If using it with AWS, use discrete SDK clients!__
164+
#### If using it with AWS, use discrete SDK clients!
153165

154166
The official [aws-sdk-js][aws-sdk] officially [supports browserify][aws-sdk-support]. That allows us to further reduce the size of our bundles (and Lambda memory usage and speed) by loading only what is strictly needed.
155167

@@ -162,7 +174,7 @@ const S3 = require('aws-sdk/clients/s3')
162174
const s3 = new S3()
163175
```
164176

165-
__Ignore AWS SDK!__
177+
#### Ignore AWS SDK!
166178

167179
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.
168180

@@ -176,6 +188,36 @@ custom:
176188
- aws-sdk/clients/s3
177189
```
178190

191+
To help you out, here's a script you can use to hide `aws-sdk` and all its clients from browserify. You can use it in your custom config for the plugin in _serverless.yml_:
192+
193+
```yml
194+
# serverless.yml
195+
196+
custom:
197+
browserify: browserify: ${file(./custom.browserify.js)}
198+
```
199+
200+
```js
201+
// custom.browserify.js
202+
//
203+
const fs = require('fs')
204+
const path = require('path')
205+
206+
module.exports = function browserifyOptions () {
207+
return {
208+
// any other valid browserify configuration...
209+
noParse: ['/**/*.json'],
210+
exclude: ['aws-sdk', ...getAllAwsSdkClients()]
211+
}
212+
}
213+
214+
function getAllAwsSdkClients () {
215+
return fs
216+
.readdirSync('./node_modules/aws-sdk/clients', { withFileTypes: true })
217+
.filter(file => file.isFile() && path.extname(file.name) === '.js')
218+
.map(file => `aws-sdk/clients/${path.basename(file.name, '.js')}`)
219+
}
220+
```
179221

180222
FAQ
181223
---
@@ -193,7 +235,7 @@ __Avoid mixing this plugin with other plugins that modify serverless' packaging
193235
This plugin _hijacks_ the normal serverless packaging process, so it will probably conflict with other plugins that use similar mechanisms.
194236

195237

196-
Useful information
238+
Useful Information
197239
------------------
198240

199241
- [List of browserify's transforms][useful-transforms-list]

index.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class BrowserifierPlugin {
1515
this.S = serverless
1616
this._b = {
1717
options,
18+
debugOn: Boolean(process.env.SLS_DEBUG) || Boolean(process.env.SLS_BROWSERIFIER_DEBUG),
1819
isDisabled: get(this.S, 'service.custom.browserify.disable', false),
1920
servicePath: path.join(this.S.config.servicePath || os.tmpdir(), '.serverless'),
2021
runtimeIsNode: get(this.S, 'service.provider.runtime', '').indexOf('nodejs') !== -1,
@@ -39,13 +40,13 @@ class BrowserifierPlugin {
3940
.then(() => {
4041
const fns = this._getAllFunctions()
4142
this.S.cli.log(`Browserifier: Preparing ${fns.length} function(s)...`)
42-
return Promise.all(fns.map(name => this._bootstrap(name).reflect()))
43+
return Promise.map(fns, name => this._bootstrap(name).reflect())
4344
})
44-
.then(results =>
45-
results
45+
.then(results => {
46+
return results
4647
.filter(inspection => inspection.isRejected())
4748
.forEach(inspection => this._handleSkip(inspection.reason()))
48-
)
49+
})
4950
.catch(this._handleSkip)
5051
.tapCatch(this._warnFailure)
5152
}

lib/bundle.js

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
'use strict'
22

33
const Promise = require('bluebird')
4-
const browserify = require('browserify')
4+
55
const archiver = require('archiver')
6+
const browserify = require('browserify')
67
const filesize = require('filesize')
78
const globby = require('globby')
89
const path = require('path')
9-
const fs = require('fs-extra')
10+
const semver = require('semver')
11+
12+
const fs = require('./fs')
1013

1114
/// //////////////////////// exports && main functions
1215

@@ -22,14 +25,15 @@ function bundle (functionName) {
2225
}
2326
return Promise.bind(this)
2427
.return(data)
28+
.then(clean)
2529
.then(prepareIncludes)
2630
.then(runBrowserify)
27-
.then(zip)
31+
.then(zipIt)
2832
.then(clean)
2933
}
3034

3135
function bootstrap (functionName) {
32-
if (process.env.SLS_DEBUG) {
36+
if (this._b.debugOn) {
3337
this.S.cli.log(`Browserifier: Preparing "${functionName}"...`)
3438
}
3539
return Promise.bind(this)
@@ -56,29 +60,34 @@ function prepareInitialData (functionName) {
5660
}
5761

5862
function cacheConfig (data) {
59-
return (this._b.functionConfigCache[data.functionName] = data)
63+
if (data) {
64+
this._b.functionConfigCache[data.functionName] = data
65+
}
66+
return data
6067
}
6168

6269
function prepareIncludes (data) {
63-
fs.emptyDirSync(data.outputFolder)
6470
const includeFiles = globby.sync(data.functionBrowserifyConfig.include, {
6571
cwd: this.S.config.servicePath,
6672
dot: true,
6773
silent: true,
6874
follow: true
6975
})
70-
if (process.env.SLS_DEBUG && includeFiles && includeFiles.length) {
71-
this.S.cli.log('Browserifier: Copying includes: ' + includeFiles)
76+
if (includeFiles && includeFiles.length) {
77+
if (this._b.debugOn) {
78+
this.S.cli.log('Browserifier: Copying includes: ' + includeFiles)
79+
}
80+
const copyFile = file => {
81+
fs.copySync(path.join(this.S.config.servicePath, file), path.join(data.outputFolder, file))
82+
}
83+
return Promise.each(includeFiles, copyFile).return(data)
7284
}
73-
includeFiles.forEach(file => {
74-
fs.copySync(path.join(this.S.config.servicePath, file), path.join(data.outputFolder, file))
75-
})
7685
return data
7786
}
7887

7988
function runBrowserify (data) {
80-
if (process.env.SLS_DEBUG) {
81-
this.S.cli.log(`Browserifier: Writing browserified bundle to ${data.outputFolder}`)
89+
if (this._b.debugOn) {
90+
this.S.cli.log(`Browserifier: Browserifying ${data.functionName}...`)
8291
}
8392
const cfg = data.functionBrowserifyConfig
8493
const b = browserify(cfg)
@@ -87,27 +96,36 @@ function runBrowserify (data) {
8796
cfg.external.forEach(file => b.external(file))
8897
return Promise.fromCallback(cb => b.bundle(cb))
8998
.then(bundleBuffer => {
99+
if (this._b.debugOn) {
100+
this.S.cli.log(`Browserifier: Writing browserified bundle to ${data.outputFolder}...`)
101+
}
90102
let handlerPath = data.functionObject.handler.split('.')[0] + '.js'
91103
handlerPath = path.join(data.outputFolder, handlerPath)
92-
fs.mkdirsSync(path.dirname(handlerPath), '0777') // handler may be in a subdir
104+
this.S.utils.writeFileDir(handlerPath)
93105
return Promise.fromCallback(cb => fs.writeFile(handlerPath, bundleBuffer, cb))
94106
})
107+
.tap(() => {
108+
if (this._b.debugOn) {
109+
this.S.cli.log(`Browserifier: Browserified output dumped to ${data.outputFolder}...`)
110+
}
111+
})
95112
.return(data)
96113
}
97114

98-
function zip (data) {
99-
const outputFile = data.functionObject.artifact || data.functionObject.package.artifact
100-
if (process.env.SLS_DEBUG) {
101-
this.S.cli.log(`Browserifier: Zipping ${data.outputFolder} to ${outputFile}`)
115+
function zipIt (data) {
116+
if (this._b.debugOn) {
117+
this.S.cli.log(`Browserifier: Zipping ${data.outputFolder} to ${data.outputBundle}...`)
102118
}
103119
const handleStream = (resolve, reject) => {
104-
const output = fs.createWriteStream(outputFile)
105-
const archive = archiver.create('zip')
106-
output.on('close', () => resolve(archive.pointer()))
107-
archive.on('error', err => reject(err))
108-
archive.pipe(output)
109-
archive.directory(data.outputFolder, '')
110-
archive.finalize()
120+
const output = fs.getNewFileStream(data.outputBundle)
121+
const zip = archiver.create('zip', { zlib: { level: 9 } })
122+
output.on('close', () => resolve(zip.pointer()))
123+
zip.on('error', err => reject(err))
124+
output.on('open', () => {
125+
zip.pipe(output)
126+
zip.directory(data.outputFolder, '')
127+
zip.finalize()
128+
})
111129
}
112130
return new Promise(handleStream).then(sizeInBytes => {
113131
this.S.cli.log(`Browserifier: Created ${data.functionName}.zip (${filesize(sizeInBytes)})...`)
@@ -116,30 +134,20 @@ function zip (data) {
116134
}
117135

118136
function clean (data) {
119-
fs.removeSync(data.outputFolder)
120-
if (fs.existsSync(data.workaroundFilePath)) {
121-
fs.removeSync(data.workaroundFilePath)
137+
if (fs.existsSync(data.outputFolder)) {
138+
return fs.removeSync(data.outputFolder)
122139
}
123-
delete this._b.functionConfigCache[data.functionName]
140+
return data
124141
}
125142

126143
function fixServerlessConfig (data) {
127-
const wafp = path.join(this._b.servicePath, 'fool-serverless.txt')
128-
data.workaroundFilePath = path.relative(this.S.config.servicePath, wafp)
129-
return fs
130-
.ensureFile(data.workaroundFilePath)
131-
.then(() => {
132-
return Promise.fromCallback(cb => {
133-
return fs.writeFile(data.workaroundFilePath, 'fool packaging step', cb)
134-
})
135-
})
136-
.then(() => {
137-
data.functionObject.package = {
138-
individually: true,
139-
exclude: ['**/*'],
140-
include: [data.workaroundFilePath],
141-
artifact: data.outputBundle
142-
}
143-
return data
144-
})
144+
if (semver.lt(this.S.getVersion(), '1.18.0')) {
145+
data.functionObject.artifact = data.outputBundle
146+
data.functionObject.package = Object.assign({}, data.functionObject.package, { disable: true })
147+
} else {
148+
data.functionObject.package = {
149+
artifact: data.outputBundle
150+
}
151+
}
152+
return data
145153
}

lib/configure.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module.exports = {
99
* Compute the base configuration
1010
*/
1111
_computeGlobalConfig () {
12-
if (process.env.SLS_DEBUG) {
12+
if (this._b.debugOn) {
1313
this.S.cli.log('Browserifier: Computing global config...')
1414
}
1515
const globalCustom = get(this.S, 'service.custom.browserify', {})
@@ -28,7 +28,7 @@ module.exports = {
2828
this._b.globalBrowserifyConfig.include = this.S.service.package.include
2929
}
3030
}
31-
if (process.env.SLS_DEBUG) {
31+
if (this._b.debugOn) {
3232
const computed = JSON.stringify(this._b.globalBrowserifyConfig)
3333
this.S.cli.log('Browserifier: Computed globalBrowserifyConfig: ' + computed)
3434
}
@@ -58,15 +58,15 @@ module.exports = {
5858
standalone: 'lambda',
5959
ignoreMissing: true, // Do not fail on missing optional dependencies
6060
detectGlobals: true, // We don't care if its slower, we want more mods to work
61-
debug: Boolean(process.env.SLS_DEBUG)
61+
debug: this._b.debugOn
6262
}
6363
if (semver.gte(version, '16.1.0')) {
64-
if (process.env.SLS_DEBUG) {
64+
if (this._b.debugOn) {
6565
this.S.cli.log('Browserifier: Using browserify "node" option...')
6666
}
6767
config.node = true
6868
} else {
69-
if (process.env.SLS_DEBUG) {
69+
if (this._b.debugOn) {
7070
this.S.cli.log('Browserifier: Using browserify "legacy" options...')
7171
}
7272
Object.assign(config, {
@@ -110,7 +110,7 @@ module.exports = {
110110
this._b.globalBrowserifyConfig,
111111
functionObject.browserify || {}
112112
)
113-
if (process.env.SLS_DEBUG) {
113+
if (this._b.debugOn) {
114114
const computed = JSON.stringify(functionObject)
115115
this.S.cli.log(`Browserifier: functionObject for ${functionName}: ${computed}`)
116116
}
@@ -134,7 +134,7 @@ module.exports = {
134134
)
135135
}
136136
}
137-
if (process.env.SLS_DEBUG) {
137+
if (this._b.debugOn) {
138138
const computed = JSON.stringify(functionBrowserifyConfig)
139139
this.S.cli.log('Browserifier: Computed function BrowserifierConfig: ' + computed)
140140
}

0 commit comments

Comments
 (0)