serverless / serverless

⚡ Serverless Framework – Effortlessly build apps that auto-scale, incur zero costs when idle, and require minimal maintenance using AWS Lambda and other managed cloud services.
https://serverless.com
MIT License
46.43k stars 5.71k forks source link

update aws-nodejs-typescript template with better defaults #6874

Closed khaledosman closed 4 years ago

khaledosman commented 5 years ago

Feature Proposal

Add better defaults and best practices for the current typescript setup for a smaller bundle size and a more performant build/compilation time as well as better developer experience

Description

I feel like the current aws-nodejs-typescript template can be improved in various ways, this is what I did for my own project and I would be happy to update the current template with it:

  1. Improving Webpack build performance.
  2. Setting up Webpack Node Externals plugin to reduce the overall bundle size and thus reducing cold start penalty.
  3. Setting up linting with ESLint
  4. use 'cheap-module-eval-source-map' in development/offline mode for faster build
  5. Exclude unnecessary folders like node_modules from ts-loader, webpack as well as tsconfig.json setup for faster compilation time and to improve typescript's/vscode's type inference performance
  6. set up fork-ts-checker-webpack-plugin and other recommendedations mentioned in https://webpack.js.org/guides/build-performance to run typechecking in a separate process
  7. vscode debugging via serverless-offline plugin
  8. reusing http connections via AWS_NODEJS_CONNECTION_REUSE_ENABLED env variable https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/node-reusing-connections.html
hakimio commented 5 years ago

@khaledosman Can you share your webpack & serverless configs?

khaledosman commented 5 years ago

webpack.config.js

const path = require('path')
const slsw = require('serverless-webpack')
const nodeExternals = require('webpack-node-externals')
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')

// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
  context: __dirname,
  mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
  entry: slsw.lib.entries,
  devtool: slsw.lib.webpack.isLocal ? 'cheap-module-eval-source-map' : 'source-map',
  resolve: {
    extensions: ['.mjs', '.json', '.ts'],
    symlinks: false,
    cacheWithContext: false
  },
  output: {
    libraryTarget: 'commonjs',
    path: path.join(__dirname, '.webpack'),
    filename: '[name].js'
  },
  target: 'node',
  externals: [nodeExternals()],
  module: {
    rules: [
      // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
      {
        test: /\.(tsx?)$/,
        loader: 'ts-loader',
        exclude: [
          [
            path.resolve(__dirname, 'node_modules'),
            path.resolve(__dirname, '.serverless'),
            path.resolve(__dirname, '.webpack'),
            path.resolve(__dirname, 'scripts')
          ]
        ],
        options: {
          transpileOnly: true,
          experimentalWatchApi: true
        }
      }
    ]
  },
  plugins: [
  //   new BundleAnalyzerPlugin()
    new ForkTsCheckerWebpackPlugin({
      eslint: true,
      eslintOptions: {
        cache: true
      }
    })
  ]
}

tsconfig.json

{
  "compilerOptions": {
    "lib": [
      "es2017"
    ],
    "removeComments": true,
    "moduleResolution": "node",
    "noUnusedLocals": false,
    "noUnusedParameters": true,
    "sourceMap": true,
    "target": "es2017",
    "outDir": "lib"
  },
  "include": [
    "./**/*.ts"
  ],
  "exclude": [
    "node_modules/**/*",
    ".serverless/**/*",
    ".webpack/**/*",
    "_warmup/**/*"
  ]
}

.eslintrc.js

module.exports = {
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "env": {
    "node": true
  },
  "parser": "@typescript-eslint/parser",
  "plugins": [
    "@typescript-eslint",
  ],
  "settings": {
    "import/parsers": {
      "@typescript-eslint/parser": [
        ".ts",
        ".tsx"
      ]
    },
    "import/resolver": {
      "typescript": {}
    }
  },
  "parserOptions": {
    "project": "./tsconfig.json",
    "tsconfigRootDir": "./",
    "sourceType": "module",
    "ecmaVersion": 2018
  },
  "rules": {
    "no-unused-vars": "off",
    "@typescript-eslint/camelcase": "warn",
    "@typescript-eslint/no-unused-vars": "warn"
  }
}

.eslintignore

node_modules
.serverless
.webpack
_warmup
**/*.js

serverless.yml

service: hello-service
custom:
  profile: my-aws-profile
  environment: ${file(env.yml):${self:provider.stage}, file(env.yml):default}
  webpack:
    webpackConfig: ./webpack.config.js
    includeModules: true
  warmup:
    prewarm: true
    name: ${self:service}-${self:provider.stage}-lambda-warmer
    concurrency: 1

plugins:
  - serverless-offline
  - serverless-plugin-warmup
  - serverless-webpack

provider:
  name: aws
  runtime: nodejs10.x
  stage: ${opt:stage, 'dev'}
  profile: ${self:custom.aws_profile} # aws credentials profile to use
  region: ${opt:region, 'eu-central-1'}
  apiGateway:
    minimumCompressionSize: 1024
  tracing:
    apiGateway: true
    lambda: true
  environment:
    BASE_URL: ${self:custom.environment.BASE_URL}
    AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1
    NODE_ENV: PRODUCTION
    AWS_STAGE: ${self:provider.stage}

iamRoleStatements:
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - sqs:*
      Resource: "${self:custom.environment.SQS_QUEUE_ARN}"

    - Effect: Allow
      Action:
        - lambda:*
      Resource: "*"

package: # Optional deployment packaging configuration
  # include: # Specify the directories and files which should be included in the deployment package
  # - src/**
  # - handler.js
  exclude: # Specify the directories and files which should be excluded in the deployment package
    - .git/**
    - apollo.config.js
    - commitlint.config.js
    - env.yml
    - .env
    - package-lock.json
    - package.json
    - yarn.lock
    - README.md
    - scripts/**
    - .vscode/**
    - .DS_Store
  excludeDevDependencies: true

functions:
  graphqlHandler:
    handler: api/graphqlHandler.graphqlHandler
    timeout: 10
    warmup:
      enabled: true
    events:
      - http:
          path: playground
          method: get
          cors: true
      - http:
          path: graphql
          method: post
          cors: true
      - http:
          path: graphql
          method: get
          cors: true

.vscode/launch.json for offline vscode debugging via serverless-offline plugin

{
  "configurations": [
    {
      "name": "Lambda",
      "type": "node",
      "request": "launch",
      "runtimeArgs": ["--inspect", "--debug-port=9229"],
      "program": "${workspaceFolder}/node_modules/serverless/bin/serverless",
      "args": ["offline"],
      "port": 9229,
      "console": "integratedTerminal"
    }
  ]
}

I intend to make a PR with this config

hakimio commented 5 years ago

Thank you for sharing your configs. Is there any reason why you don't use tslint instead of eslint?

khaledosman commented 5 years ago

tslint is deprecated, the recommendation is to use eslint for typescript linting via @typescript-eslint packages now which was admittedly a bit more painful to setup than a normal tslint.config file, see https://medium.com/palantir/tslint-in-2019-1a144c2317a9 and https://github.com/palantir/tslint/issues/4534

hakimio commented 5 years ago

@khaledosman The article says they plan to deprecate tslint once eslint reaches feature parity. Has this happened already? Was there any official announcement about deprecation? Also, in the article they state that they plan to have "TSLint → ESLint compat" package. Is it available already? I don't think we should start using eslint until it reaches feature parity and there is official announcement about tslint deprecation.

EDIT: ok, I can see they have tslint-to-eslint-config cli tool.

khaledosman commented 5 years ago

Right they encourage people to use typescript-eslint instead, this is what they say in their roadmap:

August 1st, 2019: Stop accepting new core rules. Still accept bug fixes, minor features, and rule enhancements. Custom rules are always an option and can be maintained outside this repo. November 1st, 2019: Stop accepting features or rule enhancements (with the exception of ones that make migrating to typescript-eslint easier). Still accept bug fixes. January 1st, 2020: Stop accepting anything except security fixes. December 1st, 2020: Stop accepting any PRs 🎉

typescript-eslint package already adds support for typescript in eslint, you can checkout their README regarding concerns around tslint

typescript also migrated their own codebase to typescript-eslint here

protip: use the --cache flag with your eslint **/*.ts command, it will save you lots of time

hakimio commented 4 years ago

@khaledosman just a recommendation for a couple of nice webpack plugins which I personally find quite useful and might be a good fit for the template: