packExternalModules() {
    if (this.skipCompile) {
      return BbPromise.resolve();
    }

    const stats = this.compileStats;

    const includes = this.configuration.includeModules;

    if (!includes) {
      return BbPromise.resolve();
    }
    if (this.log) {
      this.log.verbose('Packing external modules');
      this.progress.get('webpack').notice('[Webpack] Packing external modules');
    }

    // Read plugin configuration
    const packageForceIncludes = _.get(includes, 'forceInclude', []);
    const packageForceExcludes = _.get(includes, 'forceExclude', []);
    const packagePath = includes.packagePath || './package.json';
    const nodeModulesRelativeDir = includes.nodeModulesRelativeDir;
    const packageJsonPath = path.join(process.cwd(), packagePath);
    const packageScripts = _.reduce(
      this.configuration.packagerOptions.scripts || [],
      (__, script, index) => {
        __[`script${index}`] = script;
        return __;
      },
      {}
    );

    // Determine and create packager
    return BbPromise.try(() => Packagers.get.call(this, this.configuration.packager)).then(packager => {
      // Fetch needed original package.json sections
      const sectionNames = packager.copyPackageSectionNames(this.configuration.packagerOptions);
      const packageJson = this.serverless.utils.readFileSync(packageJsonPath);
      const packageSections = _.pick(packageJson, sectionNames);
      if (!_.isEmpty(packageSections)) {
        if (this.log) {
          this.log.verbose(`Using package.json sections ${_.join(_.keys(packageSections), ', ')}`);
        } else {
          this.options.verbose &&
            this.serverless.cli.log(`Using package.json sections ${_.join(_.keys(packageSections), ', ')}`);
        }
      }

      // Get first level dependency graph
      if (this.log) {
        this.log.verbose(`Fetch dependency graph from ${packageJsonPath}`);
      } else {
        this.options.verbose && this.serverless.cli.log(`Fetch dependency graph from ${packageJsonPath}`);
      }

      return packager
        .getProdDependencies(path.dirname(packageJsonPath), 1, this.configuration.packagerOptions)
        .then(dependencyGraph => {
          const problems = _.get(dependencyGraph, 'problems', []);
          if (this.options.verbose && !_.isEmpty(problems)) {
            if (this.log) {
              this.log.verbose(`Ignoring ${_.size(problems)} NPM errors:`);
            } else {
              this.serverless.cli.log(`Ignoring ${_.size(problems)} NPM errors:`);
            }
            _.forEach(problems, problem => {
              if (this.log) {
                this.log.verbose(`=> ${problem}`);
              } else {
                this.serverless.cli.log(`=> ${problem}`);
              }
            });
          }

          // (1) Generate dependency composition
          const compositeModules = _.uniq(
            _.flatMap(stats.stats, compileStats => {
              const externalModules = _.concat(
                compileStats.externalModules,
                _.map(packageForceIncludes, whitelistedPackage => ({
                  external: whitelistedPackage
                }))
              );
              return getProdModules.call(
                this,
                externalModules,
                packagePath,
                nodeModulesRelativeDir,
                dependencyGraph,
                packageForceExcludes
              );
            })
          );
          removeExcludedModules.call(this, compositeModules, packageForceExcludes, true);

          if (_.isEmpty(compositeModules)) {
            // The compiled code does not reference any external modules at all
            if (this.log) {
              this.log('No external modules needed');
            } else {
              this.serverless.cli.log('No external modules needed');
            }
            return BbPromise.resolve();
          }

          // (1.a) Install all needed modules
          const compositeModulePath = path.join(this.webpackOutputPath, 'dependencies');
          const compositePackageJson = path.join(compositeModulePath, 'package.json');

          // (1.a.1) Create a package.json
          const compositePackage = _.defaults(
            {
              name: this.serverless.service.service,
              version: '1.0.0',
              description: `Packaged externals for ${this.serverless.service.service}`,
              private: true,
              scripts: packageScripts
            },
            packageSections
          );
          const relPath = path.relative(compositeModulePath, path.dirname(packageJsonPath));
          addModulesToPackageJson(compositeModules, compositePackage, relPath);
          this.serverless.utils.writeFileSync(compositePackageJson, JSON.stringify(compositePackage, null, 2));

          // (1.a.2) Copy package-lock.json if it exists, to prevent unwanted upgrades
          const packagerOptions = this.configuration.packagerOptions || {};
          const packageLockPath = path.join(
            findWorkspaceRoot(path.dirname(packageJsonPath)) || path.dirname(packageJsonPath),
            packagerOptions.lockFile || packager.lockfileName
          );
          let hasPackageLock = false;
          return BbPromise.fromCallback(cb => fse.pathExists(packageLockPath, cb))
            .then(exists => {
              if (exists) {
                if (this.log) {
                  this.log('Package lock found - Using locked versions');
                } else {
                  this.serverless.cli.log('Package lock found - Using locked versions');
                }
                try {
                  let packageLockFile = this.serverless.utils.readFileSync(packageLockPath);
                  packageLockFile = packager.rebaseLockfile(relPath, packageLockFile);
                  if (_.isObject(packageLockFile)) {
                    packageLockFile = JSON.stringify(packageLockFile, null, 2);
                  }

                  this.serverless.utils.writeFileSync(
                    path.join(compositeModulePath, packager.lockfileName),
                    packageLockFile
                  );
                  hasPackageLock = true;
                } catch (err) {
                  if (this.log) {
                    this.log.warning(`Could not read lock file: ${err.message}`);
                  } else {
                    this.serverless.cli.log(`Warning: Could not read lock file: ${err.message}`);
                  }
                }
              }
              return BbPromise.resolve();
            })
            .then(() => {
              const start = _.now();
              if (this.log) {
                this.log('Packing external modules: ' + compositeModules.join(', '));
              } else {
                this.serverless.cli.log('Packing external modules: ' + compositeModules.join(', '));
              }

              return packager.getPackagerVersion(compositeModulePath).then(version => {
                return packager
                  .install(compositeModulePath, this.configuration.packagerOptions, version)
                  .then(() => {
                    if (this.log) {
                      this.log.verbose(`Package took [${_.now() - start} ms]`);
                    } else {
                      this.options.verbose && this.serverless.cli.log(`Package took [${_.now() - start} ms]`);
                    }
                    return null;
                  })
                  .return(stats.stats);
              });
            })
            .mapSeries(compileStats => {
              const modulePath = compileStats.outputPath;

              // Create package.json
              const modulePackageJson = path.join(modulePath, 'package.json');
              const modulePackage = _.defaults(
                {
                  name: this.serverless.service.service,
                  version: '1.0.0',
                  description: `Packaged externals for ${this.serverless.service.service}`,
                  private: true,
                  scripts: packageScripts,
                  dependencies: {}
                },
                packageSections
              );
              const prodModules = getProdModules.call(
                this,
                _.concat(
                  compileStats.externalModules,
                  _.map(packageForceIncludes, whitelistedPackage => ({
                    external: whitelistedPackage
                  }))
                ),
                packagePath,
                nodeModulesRelativeDir,
                dependencyGraph,
                packageForceExcludes
              );
              removeExcludedModules.call(this, prodModules, packageForceExcludes);
              const relPath = path.relative(modulePath, path.dirname(packageJsonPath));
              addModulesToPackageJson(prodModules, modulePackage, relPath);
              this.serverless.utils.writeFileSync(modulePackageJson, JSON.stringify(modulePackage, null, 2));

              // GOOGLE: Copy modules only if not google-cloud-functions
              //         GCF Auto installs the package json
              if (isProviderGoogle(this.serverless)) {
                return BbPromise.resolve();
              }

              const startCopy = _.now();
              return BbPromise.try(() => {
                // Only copy dependency modules if demanded by packager
                if (packager.mustCopyModules) {
                  return BbPromise.fromCallback(callback =>
                    fse.copy(
                      path.join(compositeModulePath, 'node_modules'),
                      path.join(modulePath, 'node_modules'),
                      callback
                    )
                  );
                }
                return BbPromise.resolve();
              })
                .then(() =>
                  hasPackageLock
                    ? BbPromise.fromCallback(callback =>
                        fse.copy(
                          path.join(compositeModulePath, packager.lockfileName),
                          path.join(modulePath, packager.lockfileName),
                          callback
                        )
                      )
                    : BbPromise.resolve()
                )
                .tap(() => {
                  if (this.log) {
                    this.log.verbose(`Copy modules: ${modulePath} [${_.now() - startCopy} ms]`);
                  } else {
                    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 packager.getPackagerVersion(modulePath).then(version => {
                    return packager.prune(modulePath, this.configuration.packagerOptions, version).tap(() => {
                      if (this.log) {
                        this.log.verbose(`Prune: ${modulePath} [${_.now() - startPrune} ms]`);
                      } else {
                        this.options.verbose &&
                          this.serverless.cli.log(`Prune: ${modulePath} [${_.now() - startPrune} ms]`);
                      }
                    });
                  });
                })
                .then(() => {
                  // Prune extraneous packages - removes not needed ones
                  const startRunScripts = _.now();
                  return packager.runScripts(modulePath, _.keys(packageScripts)).tap(() => {
                    if (this.log) {
                      this.log.verbose(`Run scripts: ${modulePath} [${_.now() - startRunScripts} ms]`);
                    } else {
                      this.options.verbose &&
                        this.serverless.cli.log(`Run scripts: ${modulePath} [${_.now() - startRunScripts} ms]`);
                    }
                  });
                });
            })
            .return();
        });
    });
  }
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.