DEV Community

Masui Masanori
Masui Masanori

Posted on

[TypeScript] Bundle Express application with Webpack

Intro

To create a docker image, I wanted to compile the Express application from TypeScript to JavaScript.
But I got an error because the application couldn't find TypeORM files after compiling.

This time, I will try bundling the application files with Webpack.

Environments

  • Node.js ver.16.6.1
  • Express ver.4.17.1
  • TypeScript ver.4.3.5
  • Webpack ver.5.50.0
  • webpack-cli ver.4.7.2
  • ts-loader ver.9.2.5
  • cors ver.2.8.5
  • pg ver.8.6.0
  • reflect-metadata ver.0.1.13
  • TSyringe ver.4.5.0
  • TypeoORM ver.0.2.34

Bundle an Express application

First, I create an Express application to try bundling.

index.ts

import express from 'express';
import cors from 'cors';

const port = 3098;
const app = express();

const allowlist = ['http://localhost:3000', 'http://localhost:3099']
const corsOptionsDelegate: cors.CorsOptionsDelegate<any> = (req, callback) => {
  const corsOptions = (allowlist.indexOf(req.header('Origin')) !== -1)? { origin: true }: { origin: false };
  callback(null, corsOptions);
};
app.use(express.json());
app.use(cors(corsOptionsDelegate));

app.get('/', async (req, res) => {
    res.json({ message: 'hello' });
});
app.listen(port, () => {
    console.log(`Example app listening at http://localhost:${port}`)
});
Enter fullscreen mode Exit fullscreen mode

webpack.config.js

var path = require('path');
module.exports = {
    mode: 'development',
    entry: {
        'index': './index.ts',
    },
    target: 'node',
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/
            }
        ]
    },
    resolve: {
        extensions: [ '.tsx', '.ts', '.js' ]
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, './js'),
    }
};
Enter fullscreen mode Exit fullscreen mode

Although I can get bundled file and execute it by "node js/index.js", I get a warning like below.

WARNING in ./node_modules/express/lib/view.js 81:13-25
Critical dependency: the request of a dependency is an expression
 @ ./node_modules/express/lib/application.js 22:11-28
 @ ./node_modules/express/lib/express.js 18:12-36
 @ ./node_modules/express/index.js 11:0-41
 @ ./index.ts 42:32-50

1 warning has detailed information that is not shown.
Use 'stats.errorDetails: true' resp. '--stats-error-details' to show it.
Enter fullscreen mode Exit fullscreen mode

To avoid the warning, I add "webpack-node-externals".

webpack.config.js

const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
    mode: 'development',
    entry: {
        'index': './index.ts',
    },
    target: 'node',
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/
            }
        ]
    },
    resolve: {
        extensions: [ '.tsx', '.ts', '.js' ]
    },
    externals: [nodeExternals()],
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, './js'),
    }
};
Enter fullscreen mode Exit fullscreen mode

Some samples on some web sites write like below instead of using "nodeExternals()".

...
    externals: ['express'],
...
Enter fullscreen mode Exit fullscreen mode

Although I can bundle the application, but when I running it, I will get an error.

C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:62
module.exports = express;
                 ^

ReferenceError: express is not defined
    at Object.express (C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:62:18)
    at __webpack_require__ (C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:86:42)
    at eval (webpack://webpack-sample/./index.ts?:42:33)
    at Object../index.ts (C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:40:1)
    at __webpack_require__ (C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:86:42)
    at C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:97:37
    at Object.<anonymous> (C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:99:12)
...
Enter fullscreen mode Exit fullscreen mode

Using TypeORM

Because I wanted to use TypeORM like the project what I had created last time, I added TypeORM into the sample.

Although I could add it and execute "npx webpack", I got an error on runtime.

C:\Users\example\OneDrive\Documents\workspace\bookstore_sample\entities\author.ts:1
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1031:15)
    at Module._compile (node:internal/modules/cjs/loader:1065:27)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:1005:19)
    at require (node:internal/modules/cjs/helpers:94:18)
...
Enter fullscreen mode Exit fullscreen mode

I think it's because entity classes are loaded dynamically.
So if I change the code to just connecting the Database and avoiding accessing tables with entity classes, the error won't be occurred.

Now I use "ormconfig.json" for configuration.

ormconfig.json

{
    "type": "postgres",
    "host": "localhost",
    "port": 5432,
    "username": "postgres",
    "password": "example",
    "database": "book_store",
    "synchronize": false,
    "logging": false, 
    "entities": ["./entities/*.{ts,js}"],
    "migrations": ["./migrations/*.{ts,js}"],
    "cli": {
       "entitiesDir": "entities",
       "migrationsDir": "migrations"
    }
 }
Enter fullscreen mode Exit fullscreen mode

From the result, I moved the config data into "createConnection".

dataContext.ts (Before)

import "reflect-metadata";
import { singleton } from "tsyringe";
import { Connection, createConnection } from "typeorm";

@singleton()
export class DataContext {
    private connection: Connection|null = null;
    public async getConnection(): Promise<Connection> {
        if(this.connection != null) {
            return this.connection;
        }
        this.connection = await createConnection();
        return this.connection;
    } 
}
Enter fullscreen mode Exit fullscreen mode

dataContext.ts (After)

import "reflect-metadata";
import { singleton } from "tsyringe";
import { Connection, createConnection } from "typeorm";
import { Author } from "../entities/author";
import { Book } from "../entities/book";
import { Genre } from "../entities/genre";

@singleton()
export class DataContext {
    private connection: Connection|null = null;
    public async getConnection(): Promise<Connection> {
        if(this.connection != null) {
            return this.connection;
        }
        this.connection = await createConnection({
            type: "postgres",
            host: "localhost",
            port: 5432,
            username: "postgres",
            password: "example",
            database: "book_store",
            synchronize: false,
            logging: false,
            entities: [Author, Book, Genre]
        });
        return this.connection;
    } 
}
Enter fullscreen mode Exit fullscreen mode

For production, maybe I have to change configuration data like "host".

Discussion (0)