Repository: serverless-heaven/serverless-webpack (js)

Issue:
Throw error when webpack entry is configured and package individually is true
<!--
1. Please check if an issue already exists so there are no duplicates
2. Fill out the whole template so we have a good overview on the issue
3. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
4. Please follow the template, otherwise we'll have to ask you to update it
-->

# This is a Feature Proposal

## Description

Context: When package individually is set to true, webpack entry should not be configured at all. This is currently the expected behaviour. It is however not explicit enough to the plugin user and it will fail with some other error.

Proposal: Throw an error when webpack entry is configured and package individually is set to true. 

Related to:
* #266. See discussion [here](https://github.com/serverless-heaven/serverless-webpack/issues/266#issuecomment-341892898).

## Additional Data

* ***Serverless-Webpack Version you're using***:
3.1.2

* ***Webpack version you're using***:
3.5.5

* ***Serverless Framework Version you're using***:
1.23.0

* ***Operating System***:
OS X 10.10.5

* ***Stack Trace (if available)***:
```
Type error:

  Type Error ---------------------------------------------

  Path must be a string. Received [ 'babel-polyfill',
  'source-map-support/register',
  './packages/core/src/alexa-skill.js' ]

     For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable.


Stack trace:
  Stack Trace --------------------------------------------

TypeError: Path must be a string. Received [ 'babel-polyfill',
  'source-map-support/register',
  './packages/core/src/alexa-skill.js' ]
    at assertPath (path.js:28:11)
    at Object.relative (path.js:1248:5)
    at entryFunctions._.flatMap (/Users/ceilfors/github/realtime-ww2-alexa/node_modules/serverless-webpack/lib/validate.js:154:28)
    at /Users/ceilfors/github/realtime-ww2-alexa/node_modules/lodash/lodash.js:3554:27
    at /Users/ceilfors/github/realtime-ww2-alexa/node_modules/lodash/lodash.js:4944:15
    at baseForOwn (/Users/ceilfors/github/realtime-ww2-alexa/node_modules/lodash/lodash.js:3001:24)
    at /Users/ceilfors/github/realtime-ww2-alexa/node_modules/lodash/lodash.js:4913:18
    at baseMap (/Users/ceilfors/github/realtime-ww2-alexa/node_modules/lodash/lodash.js:3553:7)
    at map (/Users/ceilfors/github/realtime-ww2-alexa/node_modules/lodash/lodash.js:9571:14)
    at Function.flatMap (/Users/ceilfors/github/realtime-ww2-alexa/node_modules/lodash/lodash.js:9274:26)
    at ServerlessWebpack.validate (/Users/ceilfors/github/realtime-ww2-alexa/node_modules/serverless-webpack/lib/validate.js:153:31)
From previous event:
    at PluginManager.invoke (/Users/ceilfors/.nvm/versions/node/v8.1.2/lib/node_modules/serverless/lib/classes/PluginManager.js:360:22)
    at PluginManager.run (/Users/ceilfors/.nvm/versions/node/v8.1.2/lib/node_modules/serverless/lib/classes/PluginManager.js:391:17)
    at variables.populateService.then (/Users/ceilfors/.nvm/versions/node/v8.1.2/lib/node_modules/serverless/lib/Serverless.js:99:33)
    at runCallback (timers.js:800:20)
    at tryOnImmediate (timers.js:762:5)
    at processImmediate [as _immediateCallback] (timers.js:733:5)
From previous event:
    at Serverless.run (/Users/ceilfors/.nvm/versions/node/v8.1.2/lib/node_modules/serverless/lib/Serverless.js:86:74)
    at serverless.init.then (/Users/ceilfors/.nvm/versions/node/v8.1.2/lib/node_modules/serverless/bin/serverless:39:50)
    at <anonymous>
```


Pull request description:
Default entry individually
<!--
1. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the PR
2. Please follow the template, otherwise we'll have to ask you to update it and it will take longer until your PR is merged
-->

## What did you implement:

Closes #272.

Mainly:
- Default webpackConfig entry to lib.entries if package.individually is set to true.
- Throw error if entry is set to anything else other than empty value or lib.entries.

<!--
Briefly describe the feature if no issue exists for this PR. If possible only
submit PRs for existing issues. If the PR is trivial (like doc changes or simple
code fixes) it can be submitted without a related issue, but as soon as it adds
or changes functionality, a related issue should be present.
-->

## How did you implement it:

<!--
If this is a nontrivial change please briefly describe your implementation so its easy for us to understand and review your code.
-->
Trivial change.

## How can we verify it:

<!--
Add any applicable config, commands, screenshots or other resources
to make it easy for us to verify this works. The easier you make it for us
to review a PR, the faster we can review and merge it.

Examples:
* Step by step description, how to verify
* Screenshots - Showing the difference between your output and the master
* Other - Anything else that comes to mind to help us evaluate
-->
New behaviour can be seen in the unit tests.

## Todos:

- [x] Write tests
- [x] Write documentation
- [x] Fix linting errors
- [x] Make sure code coverage hasn't dropped
- [ ] Provide verification config / commands / resources
- [x] Enable "Allow edits from maintainers" for this PR
- [x] Update the messages below

***Is this ready for review?:*** YES
***Is it a breaking change?:*** NO (Do assess)


Patch:
diff --git a/README.md b/README.md
index f7c4605..fcd34fe 100644
--- a/README.md
+++ b/README.md
@@ -281,7 +281,9 @@ 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 will automatically apply the _automatic entry resolution_ (see above) and
+you will not be able to configure the entry config in webpack. An error will be thrown
+if you are trying to override the entry in webpack.config.js with other unsupported values.
 
 The individual packaging needs more time at the packaging phase, but you'll
 get that paid back twice at runtime.
diff --git a/lib/validate.js b/lib/validate.js
index 1c762fd..c2e501f 100644
--- a/lib/validate.js
+++ b/lib/validate.js
@@ -21,6 +21,14 @@ const preferredExtensions = [
 
 module.exports = {
   validate() {
+    const getHandlerFile = handler => {
+      // Check if handler is a well-formed path based handler.
+      const handlerEntry = /(.*)\..*?$/.exec(handler);
+      if (handlerEntry) {
+        return handlerEntry[1];
+      }
+    };
+
     const getEntryExtension = fileName => {
       const files = glob.sync(`${fileName}.*`, {
         cwd: this.serverless.config.servicePath,
@@ -51,14 +59,13 @@ module.exports = {
 
     const getEntryForFunction = (name, serverlessFunction) => {
       const handler = serverlessFunction.handler;
-      // Check if handler is a well-formed path based handler.
-      const handlerEntry = /(.*)\..*?$/.exec(handler);
-      if (!handlerEntry) {
+      
+      const handlerFile = getHandlerFile(handler);
+      if (!handlerFile) {
         _.get(this.serverless, 'service.provider.name') !== 'google' &&
           this.serverless.cli.log(`\nWARNING: Entry for ${name}@${handler} could not be retrieved.\nPlease check your service config if you want to use lib.entries.`);
         return {};
       }
-      const handlerFile = handlerEntry[1];
       const ext = getEntryExtension(handlerFile);
 
       // Create a valid entry key
@@ -135,13 +142,19 @@ module.exports = {
       this.options.verbose && this.serverless.cli.log('Using multi-compile (individual packaging)');
       this.multiCompile = true;
 
+      if (this.webpackConfig.entry && !_.isEqual(this.webpackConfig.entry, entries)) {
+        return BbPromise.reject(new this.serverless.classes
+          .Error('Webpack entry must be automatically resolved when package.individually is set to true. ' +
+            'In webpack.config.js, remove the entry declaration or set entry to slsw.lib.entries.'));
+      }
+
       // 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]);
+          const handlerFile = path.relative('.', getHandlerFile(handler));
           return {
             handlerFile,
             funcName,
@@ -150,7 +163,7 @@ module.exports = {
         }
       );
 
-      this.entryFunctions = _.flatMap(this.webpackConfig.entry, (value, key) => {
+      this.entryFunctions = _.flatMap(entries, (value, key) => {
         const entry = path.relative('.', value);
         const entryFile = _.replace(entry, new RegExp(`${path.extname(entry)}$`), '');
 


Test patch:
diff --git a/tests/validate.test.js b/tests/validate.test.js
index 16d6b8f..8a23d19 100644
--- a/tests/validate.test.js
+++ b/tests/validate.test.js
@@ -436,12 +436,6 @@ describe('validate', () => {
 
       describe('package individually', () => {
         const testConfig = {
-          entry: {
-            module1: './module1.js',
-            module2: './module2.js',
-            'handlers/func3/module2': './handlers/func3/module2.js',
-            'handlers/module2/func3/module2': './handlers/module2/func3/module2.js'
-          },
           output: {
             path: 'output',
           },
@@ -469,6 +463,29 @@ describe('validate', () => {
           });
         });
 
+        it('should fail if webpackConfig.entry is customised', () => {
+          module.serverless.service.custom.webpack = _.merge({}, testConfig, {
+            entry: {
+              module1: './module1.js',
+              module2: './module2.js'
+            }
+          });
+          module.serverless.service.functions = testFunctionsConfig;
+          globSyncStub.callsFake(filename => [_.replace(filename, '*', 'js')]);
+          return expect(module.validate()).to.be.rejectedWith(
+            /Webpack entry must be automatically resolved when package.individually is set to true/);
+        });
+
+        it('should not fail if webpackConfig.entry is set to lib.entries for backward compatibility', () => {
+          const lib = require('../lib/index');
+          module.serverless.service.custom.webpack = _.merge({}, testConfig, {
+            entry: lib.entries
+          });
+          module.serverless.service.functions = testFunctionsConfig;
+          globSyncStub.callsFake(filename => [_.replace(filename, '*', 'js')]);
+          return expect(module.validate()).to.be.fulfilled;
+        });
+
         it('should expose all functions details in entryFunctions property', () => {
           module.serverless.service.custom.webpack = testConfig;
           module.serverless.service.functions = testFunctionsConfig;