From b4fa0a53fb32ca707fc6c8b24c0e8dac698ed7b4 Mon Sep 17 00:00:00 2001 From: Tony Yuen Date: Wed, 7 Sep 2016 11:55:02 +0800 Subject: [PATCH] support external-module-auto-packaging --- README.md | 34 +++-- .../package.json | 7 +- .../serverless.yml | 3 +- .../webpack.config.js | 4 +- index.js | 7 +- lib/compile.js | 1 + lib/packExternalModules.js | 123 ++++++++++++++---- 7 files changed, 133 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 56d9e07..1e0e479 100644 --- a/README.md +++ b/README.md @@ -33,23 +33,39 @@ generated to write bundles in the `.webpack` directory. By default, the plugin will try to bundle all dependencies. However, you don't want to include all modules in some cases such as selectively import, excluding builtin package (aws-sdk) and handling webpack-incompatible modules. In this case, -you add all the modules, you want to exclude from bundled files, into `externals` field -of your `webpack.config.json` and add those, you want to include in final distribution, -into `serverless.yml`: - -```javascript -// webpack.config.json -{ - externals: ["module1", "module2"] // modules to be excluded from bundled file +you can enable external-module-auto-packaging feature by setting `webpackIncludeModules` +custom key to be true and defines your webpack `externals` in `webpack.config.json`. +All modules stated in `externals` will be excluded from bundled files. If an excluded module +is stated as `dependencies` in `package.json`, it will be packed into node_modules in +artifact. + +```js +// webpack.config.js +var nodeExternals = require('webpack-node-externals') + +modules.export = { + // we use webpack-node-externals to excludes all node deps. You can manually set + // the externals too. + externals: [nodeExternals()], } ``` +```yaml +# serverless.yml +custom: + webpackIncludeModules: true # enable auto-packing +``` + +By default, the plugin will use the `package.json` file in working directory, If you want to +use a different package conf, set `packagePath` to your custom package.json. eg: + ```yaml # serverless.yml custom: webpackIncludeModules: - - module1 # modules to be included in distribution + packagePath: '../package.json' # relative path to custom package.json file. ``` +> Noted that only relative path is supported now. You can find an example setup in the [`examples`](./examples) folder. diff --git a/examples/include-external-npm-packages/package.json b/examples/include-external-npm-packages/package.json index b13ee30..8601b2f 100644 --- a/examples/include-external-npm-packages/package.json +++ b/examples/include-external-npm-packages/package.json @@ -10,10 +10,11 @@ "license": "MIT", "devDependencies": { "serverless-webpack": "^1.0.0-beta.2", - "webpack": "^1.13.1" + "webpack": "^1.13.1", + "webpack-node-externals": "^1.3.3", + "aws-sdk": "^2.5.3" // packages in devDependencies won't be included in distribution }, - "dependencies": { - "aws-sdk": "^2.5.3", + "dependencies": { // packages in dependencies will be included in distribution if required "fbgraph": "^1.3.0" } } diff --git a/examples/include-external-npm-packages/serverless.yml b/examples/include-external-npm-packages/serverless.yml index d27dd6a..5cfa396 100644 --- a/examples/include-external-npm-packages/serverless.yml +++ b/examples/include-external-npm-packages/serverless.yml @@ -9,8 +9,7 @@ provider: runtime: nodejs4.3 custom: - webpackIncludeModules: # modules to be included in distribution - - fbgraph + webpackIncludeModules: true # enable auto including modules functions: first: diff --git a/examples/include-external-npm-packages/webpack.config.js b/examples/include-external-npm-packages/webpack.config.js index 846684e..02f1fed 100644 --- a/examples/include-external-npm-packages/webpack.config.js +++ b/examples/include-external-npm-packages/webpack.config.js @@ -1,7 +1,7 @@ -var path = require('path'); +var nodeExternals = require('webpack-node-externals'); module.exports = { entry: './handler.js', target: 'node', - externals: ["fbgraph", "aws-sdk"] // modules to be excluded from bundled file + externals: [nodeExternals()] // exclude external modules }; diff --git a/index.js b/index.js index 73160b6..bdb70fd 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,7 @@ const compile = require('./lib/compile'); const cleanup = require('./lib/cleanup'); const run = require('./lib/run'); const serve = require('./lib/serve'); -const packExternalModules = require('./lib/packExternalModules') +const packExternalModules = require('./lib/packExternalModules'); class ServerlessWebpack { constructor(serverless, options) { @@ -30,7 +30,6 @@ class ServerlessWebpack { lifecycleEvents: [ 'validate', 'compile', - 'packExternalModules', ], options: { out: { @@ -102,9 +101,7 @@ class ServerlessWebpack { .then(this.validate), 'webpack:compile': () => BbPromise.bind(this) - .then(this.compile), - - 'webpack:packExternalModules': () => BbPromise.bind(this) + .then(this.compile) .then(this.packExternalModules), 'webpack:invoke:invoke': () => BbPromise.bind(this) diff --git a/lib/compile.js b/lib/compile.js index ccee537..259c125 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -12,6 +12,7 @@ module.exports = { return BbPromise .fromCallback(cb => compiler.run(cb)) .then(stats => { + this.serverless.cli.consoleLog(stats.toString({ colors: true, hash: false, diff --git a/lib/packExternalModules.js b/lib/packExternalModules.js index 0e8e9c0..3843051 100644 --- a/lib/packExternalModules.js +++ b/lib/packExternalModules.js @@ -5,38 +5,111 @@ const fs = require('fs'); const path = require('path'); const npm = require('npm-programmatic'); +function getProdModules(externalModules, packagePath) { + + const packageJson = require(path.join(process.cwd(), packagePath)); + + const prodModules = []; + + // only process the module stated in dependencies section + if (!packageJson.dependencies) { + return [] + } + + externalModules.forEach(module => { + + const moduleVersion = packageJson.dependencies[module]; + + if (moduleVersion) { + prodModules.push(`${module}@${moduleVersion}`); + } + }); + + return prodModules; +} + +function getExternalModuleName(module) { + + const path = /^external "(.*)"$/.exec(module.identifier())[1]; + + + const pathComponents = path.split('/'); + + const main = pathComponents[0]; + + // this is a package within a namespace + if (main.charAt(0) == '@') { + return `${main}/${pathComponents[1]}` + } + + return main +} + +function isExternalModule(module) { + return module.identifier().indexOf('external ') === 0; +} + +function getExternalModules(stats) { + + const externals = new Set(); + + stats.compilation.chunks.forEach(function(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: + if (isExternalModule(module)) { + externals.add(getExternalModuleName(module)); + } + }); + }); + + return Array.from(externals); +} + module.exports = { - packExternalModules() { + packExternalModules(stats) { const includes = ( - this.serverless.service.custom && - this.serverless.service.custom.webpackIncludeModules + this.serverless.service.custom && + this.serverless.service.custom.webpackIncludeModules ); return BbPromise.resolve().then(() => { - if (!includes || includes.length === 0) { - return; - } - - this.serverless.cli.log('Packing external modules: ' + includes.join(",")); - const tmpPackageJson = path.join(this.serverless.config.servicePath, 'package.json'); + if (!includes) { + return; + } + + const packagePath = includes.packagePath || './package.json'; + + const externalModules = getExternalModules(stats); + + // this plugin will only install modules stated in dependencies section of package.json + const prodModules = getProdModules(externalModules, packagePath); + + if (prodModules.length === 0) { + return; + } + + this.serverless.cli.log('Packing external modules: ' + prodModules.join(", ")); + + 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, "{}"); - - return new BbPromise((resolve, reject) => { - npm.install(includes, { - cwd: this.serverless.config.servicePath, - save: false - }).then(() => { - fs.unlink(tmpPackageJson); - resolve() - }).catch(e => { - fs.unlink(tmpPackageJson); - reject(e); - }) + // create a temp package.json in dist directory so that we can install the dependencies later. + fs.writeFileSync(tmpPackageJson, "{}"); + + 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); }) - }) - }, + }) + }); + } };