Search code examples
aws-lambdayarnpkgserverless-frameworkyarn-workspaces

How can I package symbolic link in node_modules with `serverless?


I am using serverless to package nodejs application. I am using yarn workspace in my project.

- common
- projectA
- projectB

the projectA and projectB are using common module which is managed by yarn workspace. It creates a link inside node_modules/common -> ../common. But when I package the application with sls deploy, it doesn't inlude the link node_modules/common. How can I make it package symbolic link?


Solution

  • You should start using code bundler.

    What is code bundler?

    What code bundler does:

    1. It scans your AWS Lambda code structure through all of the files, starting from handler file.
    2. It goes through all of the imports to create a tree of dependencies.
    3. Then it inlines all of those dependencies into single "fat" file.
    4. After that you are free to deploy your application, which has only single file.

    As you can see, it's a perfect match for AWS Lambda and your use case.

    All of the dependencies from common package will be included in the output file.

    Also code bundlers have other cool features, like removing all of the unneeded files, that are defined in libraries that you use, but you are not using them directly. Due to this output package size of your Lambda will be a lot smaller, which will decrease cold starts.

    How to achieve that using Serveless Framework

    The easiest way is to start with serverless-webpack plugin, which includes Webpack (one of the most popular code bundlers) and some most common configurations for it.

    After adding this plugin, simply configure it in serverless.yml:

    custom:
      webpack:
        webpackConfig: 'webpack.config.js'   # you can remove it, it's the same as default
        packager: 'yarn'
    

    Now you need to configure Webpack using webpack.config.js file. There are a lot of possibilities to configure it and the example below is the most basic one:

    const path = require('path');
    const slsw = require('serverless-webpack');
    
    module.exports = {
      entry: slsw.lib.entries,
      target: 'node',
      mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
      stats: 'minimal',
      devtool: 'nosources-source-map',
      externals: [{'aws-sdk': 'commonjs aws-sdk'}],
      resolve: {
        extensions: ['.js', '.json'],
      },
      output: {
        libraryTarget: 'commonjs2',
        path: path.join(__dirname, '.webpack'),
        filename: '[name].js',
        sourceMapFilename: '[file].map',
      },
    };
    

    Now when you call sls package in projectA or projectB, then after unzipping ./.serverless/functionName.zip, you will find just single "fat" file, that will include all of the required dependencies.

    During sls deploy phase, this file will be deployed as Lambda handler.

    Correctly defining dependencies

    Make sure, that common package is listed as dependency of projectA and projectB:

    // common/package.json
    
    {
      "name": "@your-project/common",
      "version": "1.0.0",
      "license": "ISC",
    }
    
    // projectA/package.json
    
    {
      "name": "@your-project/packageA",
      "version": "1.0.0",
      "license": "ISC",
      "dependencies": {
          "@your-project/common": "1.0.0"
      }
    }
    

    Thanks to this, you will be able to reference commons in pakcageA imports via:

    import exampleHelper from '@your-project/common/src/exampleHelper';
    

    Project using this approach can be found on my Github here:

    https://github.com/PatrykMilewski/serverless-series