serverless-webpack icon indicating copy to clipboard operation
serverless-webpack copied to clipboard

How to use in Monorepo without producing tons of duplicate code.

Open khrome83 opened this issue 7 years ago • 7 comments

This is a Question / Clarification

Description

How do you use this within a Monorepo. Ideally you would define a single webpack.config.js and .babelrc file, and no matter the services, it should share that config. It seems when I specify a webpack.config.js in root, it assumes the functions are located there.

What is the best practices? Is this supported?

khrome83 avatar Aug 02 '18 13:08 khrome83

In order to have multiple functions in different folders you have to add this to your webpack confg:

module.exports = { entry: slsw.lib.entries };

It takes all functions from your serverless config which contains paths to all functions handlers. For example your serverless.yml file can look like this:

functions: trackFulfillment: handler: src/functions/track/fulfillment/handler.trackFulfillment trackValidation: handler: src/functions/track/validation/handler.trackValidation feedbackFulfillment: handler: src/functions/feedback/fulfillment/handler.feedbackFulfillment feedbackValidation: handler: src/functions/feedback/validation/handler.feedbackValidation

When configured like this you don't need to change anything in webpack config whenever new function is added.

rbobko93 avatar Aug 07 '18 14:08 rbobko93

@rbobko93 - I did discover this, but this makes sense if I have one serverless.yml file.

I am looking at a structure more like below. Each services has a package.json and a serverless.yml.

I am curious if each serverless.yml should be configured like something below - What is the best practice. Does each package.json need to have webpack and babel plugin inside of each service, or is there a way to contain this at root to reduce code?

In a traditional node monorepo, I would have a separate package.json in each separated concerns. How do you mimic that structure with the least code copy/pasted between them. Does this make more sense?

My Current Folder Structure

package.json
webpack.config.js
.babelrc
jest.config.js
resources/
  dynamo-table.yml
  s3-bucket.yml
  package.json
  serverless.yml
services/
  users/
    create.js
    create.test.js
    list.js
    list.test.js
    delete.js
    delete.test.js
    update.js
    update.test.js
    get.js
    get.test.js
    package.json
    serverless.yml
  projects/
    create.js
    create.test.js
    list.js
    list.test.js
    delete.js
    delete.test.js
    update.js
    update.test.js
    get.js
    get.test.js
    package.json
    serverless.yml
  etc/
    ...

Example Service Yaml File

service: service-name

plugins:
  - serverless-webpack
  - serverless-dynamodb-local
  - serverless-offline
  - serverless-domain-manager
  - serverless-plugin-stage-variables

custom:
  webpack:
    webpackConfig: ../../webpack.config.js
    includeModules:
      forceExclude:
        - aws-sdk
  stage: ${opt:stage, self:provider.stage}
  stageVariables:
    env: ${self:custom.stage}
  tables:
    projects: projects-${self:custom.stage}
  domains:
    prod: api.example.com
    stage: stage-api.example.com
    dev: dev-api.example.com
  customDomain:
    domainName: ${self:custom.domains.${self:custom.stage}}
    basePath: 'projects'
    stage: ${self:custom.stage}
    createRoute53Record: true
  dynamodb:
    start:
      port: 8000
      inMemory: true
      migrate: true
    migration:
      dir: migrations

package:
  exclude:
  - coverage/**
  - migrations/**
  - .circleci/**
  - .git/**
  - tests/**

provider:
  name: aws
  runtime: nodejs8.10
  stage: dev
  region: us-east-1
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: "arn:aws:dynamodb:*:*:table/${self:custom.tables.projects}"

functions:
  create:
    handler: create.default
    events:
      - http:
          path: /
          method: post
          cors: true
    environment:
      PROJECTS_TABLE: ${self:custom.tables.projects}

  list:
    handler: list.default
    events:
      - http:
          path: /
          method: get
          cors: true
    environment:
      PROJECTS_TABLE: ${self:custom.tables.projects}

  get:
    handler: get.default
    events:
      - http:
          path: /{id}
          method: get
          cors: true
    environment:
      PROJECTS_TABLE: ${self:custom.tables.projects}

  update:
    handler: update.default
    events:
      - http:
          path: /{id}
          method: put
          cors: true
    environment:
      PROJECTS_TABLE: ${self:custom.tables.projects}

  delete:
    handler: delete.default
    events:
      - http:
          path: /{id}
          method: delete
          cors: true
    environment:
      PROJECTS_TABLE: ${self:custom.tables.projects}

khrome83 avatar Aug 07 '18 22:08 khrome83

Error when running against root webpack.config.js

 Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
 - configuration.entry should be one of these:
   object { <key>: non-empty string | [non-empty string] } | non-empty string | [non-empty string] | function
   -> The entry point(s) of the compilation.
   Details:
    * configuration.entry should not be empty.
      -> Multiple entry bundles are created. The key is the chunk name. The value can be a string or an array.
    * configuration.entry should be a string.
      -> An entry point without name. The string is resolved to a module which is loaded upon startup.
    * configuration.entry should be an array:
      [non-empty string]
    * configuration.entry should be an instance of function
      -> A Function returning an entry object, an entry string, an entry array or a promise to these things.

Root Webpack Config

const slsw = require("serverless-webpack");
const nodeExternals = require("webpack-node-externals");

module.exports = {
  entry: slsw.lib.entries,
  target: "node",
  // Generate sourcemaps for proper error messages
  devtool: 'source-map',
  // Since 'aws-sdk' is not compatible with webpack,
  // we exclude all node dependencies
  externals: [nodeExternals()],
  mode: slsw.lib.webpack.isLocal ? "development" : "production",
  optimization: {
    // We do not want to minimize our code.
    minimize: false
  },
  performance: {
    // Turn off size warnings for entry points
    hints: false
  },
  // Run babel on all .js files and skip those in node_modules
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: "babel-loader",
        include: __dirname,
        exclude: /node_modules/
      }
    ]
  }
};

khrome83 avatar Aug 08 '18 02:08 khrome83

@khrome83 did you find a resolution to this problem?

I have a small Serverless project which is about to get considerably more complex and as there are only two people working on it I'm learning towards keeping a mono-repo however I'm seeing lots of people mentioning this issues so I'm unsure.

kempsterrrr avatar Apr 18 '19 13:04 kempsterrrr

It's possible to have a single webpack config file in a root, but as a result you must move "serverless-webpack" and "webpack" npm dependencies in a root package.json (and ignore warnings while npm i eg. npm WARN [email protected] requires a peer of webpack@>=2 but none is installed. You must install peer dependencies yourself.) also you should setup correct webpackConfig path in serverless.yml

Enase avatar Apr 18 '19 16:04 Enase

Hi all, just thought I'd share how we manage similar webpack configurations in a monorepo. We use Yarn Workspaces, but you should be able to do without.

We created a @org/webpack-config package that exports a single .js file with the base Webpack config.

Then in each Serverless project inside the monorepo, you can do something like:

services/users/webpack.config.js:

const slsw = require('serverless-webpack');
const baseConfig = require('@org/webpack-config');

module.exports = {
  ...baseConfig,
  entry: slsw.lib.entries,
}

hassankhan avatar Apr 26 '19 21:04 hassankhan

@hassankhan Could you give an example of what the baseConfig would look? This almost worked for me but I'm not sure if it was some wrong Typescript configuration that is preventing me from actually getting somewhere.

vamcs avatar Aug 20 '20 18:08 vamcs