6
votes

Why is webpack not including the node_module libraries in the build when they are imported into the index file?

webpack is still a bit of a black art for me that I have not fully grasped, so below is an explanation of that I am trying to achieve.

I am building a test application to run as serverless code on an AWS Lambda Node server. The goal is to keep the deployment package a small as possible by having 1 or more chunks and no node_modules folder on the server.

I am writing in Typescript v3 and using webpack v4 to create a bundle for the server.

Here is my directory structure:

  • dist/
  • node_modules/
  • src/index.ts
  • package.json
  • tsconfig.json
  • tslint.json
  • webpack.config.js

here is an excerpt from my package.json

  "dependencies": {
    "moment": "^2.22.2"
  },
  "devDependencies": {
    "@types/aws-lambda": "^8.10.15",
    "@types/node": "^10.12.2",
    "awesome-typescript-loader": "^5.2.1",
    "aws-lambda": "^0.1.2",
    "source-map-loader": "^0.2.4",
    "ts-node": "^7.0.1",
    "tslint": "^5.11.0",
    "tslint-loader": "^3.5.4",
    "typescript": "^3.1.6",
    "webpack": "^4.25.1",
    "webpack-bundle-analyzer": "^3.0.3",
    "webpack-cli": "^3.1.2",
    "webpack-node-externals": "^1.7.2"
  }

my index.ts that I am trying to bundle:

import { Handler } from "aws-lambda";
import moment from "moment";

const handler: Handler = async ( event: any ) => {
    const stamp = moment().format( "x" );
    console.log( `process: ${ process.env.NAME } called at ${ stamp }` );
    console.log( event );
    return {
        body: "process completed",
        statusCode: 200
    };
};

export { handler };

my tsconfig.json:

{
    "compilerOptions": {
        "outDir": "./dist",
        "sourceMap": true,
        "noImplicitAny": true,
        "module": "commonjs",
        "target": "es5",
        "allowJs": true,
        "pretty": false,
        "removeComments": true,
        "esModuleInterop": true,
        "moduleResolution": "node",
        "types": [
            "node"
        ],
        "lib": [
            "es5",
            "es6",
            "dom"
        ]
    },
    "include": [
        "src/**/*.ts"
    ],
    "exclude": [
        "node_modules"
    ]
}

finally here is my webpack.config.js:

const path = require( "path" );
const UglifyJSPlugin = require( "uglifyjs-webpack-plugin" );
const nodeExternals = require( "webpack-node-externals" );

const ROOT = path.resolve( __dirname, "src" );
const DESTINATION = path.resolve( __dirname, "dist" );

module.exports = {
    context: ROOT,
    mode: "production",
    entry: {
        index: "./index.ts"
    },
    target: "node",
    externals: [ nodeExternals() ],
    optimization: {
        minimizer: [
            new UglifyJSPlugin( {
                uglifyOptions: {
                    compress: true,
                    mangle: false,
                    toplevel: false,
                    keep_classnames: true,
                    keep_fnames: true
                }
            } )
        ],
        splitChunks: {
            cacheGroups: {
                node_vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    chunks: "all",
                    priority: 1
                }
            }
        }
    },
    output: {
        filename: "[name].js",
        libraryTarget: "commonjs2",
        path: DESTINATION
    },
    resolve: {
        extensions: [ ".ts", ".js" ],
        modules: [ ROOT, "node_modules" ]
    },
    module: {
        rules: [
            {
                enforce: "pre",
                test: /\.js$/,
                use: "source-map-loader"
            },
            {
                enforce: "pre",
                test: /\.ts$/,
                exclude: /node_modules/,
                use: "tslint-loader"
            },
            {
                test: /\.ts$/,
                exclude: [ /node_modules/ ],
                use: "awesome-typescript-loader"
            }
        ]
    },
    devtool: "cheap-module-source-map",
    devServer: {}
};

When I run webpack I get the following console output:

Version: webpack 4.25.1
Time: 4856ms
Built at: 2018-11-08 15:41:09
   Asset      Size  Chunks             Chunk Names
index.js  3.83 KiB       0  [emitted]  index
Entrypoint index = index.js
[0] ./index.ts 3.11 KiB {0} [built]
[1] external "moment" 42 bytes {0} [built]

When I deploy this to AWS Lambda I and call the function I get the following error:

{
    "errorMessage": "Cannot find module 'moment'",
    "errorType": "Error",
    "stackTrace": [
    "Function.Module._load (module.js:474:25)",
    "Module.require (module.js:596:17)",
    "require (internal/module.js:11:18)",
    "Object.<anonymous> (/var/task/index.js:1:3963)",
    "__webpack_require__ (/var/task/index.js:1:323)",
    "Object.<anonymous> (/var/task/index.js:1:3595)",
    "__webpack_require__ (/var/task/index.js:1:323)",
    "module.exports.__awaiter.__awaiter.P (/var/task/index.js:1:1650)",
    "Object.<anonymous> (/var/task/index.js:1:1695)"
    ]
}
1
Any news on this as I have the same issue? - user1753622
because of nodeExternals, that declares every single dependency as external (i.e: node_modules has to be present) - PlayMa256
nodeExternals was the key, now it can find the module but throws up a separate error for me to investigate. @PlayMa256 if you add it as an answer I will accept it - APW
as a side note for future readers, chunking the build causes call errors on the server as the root file does not specifically load the other chunk files, removing the splitChunks object will let webpack build one large file that works. - APW

1 Answers

4
votes

because of nodeExternals, that declares every single dependency as external (i.e: node_modules has to be present when executed).