Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Module.exports with es6 import for webpack.config.ts in typescript

I'm using webpack with typescript with the config its self in typescript. Instructions here

In my root I have webpack.config.base.ts


import webpack from "webpack";

const getConfig = ( appDir: string, distDir: string, env: any, args: any ): webpack.Configuration => {
    const config: webpack.Configuration = {
        entry: {

        },
        output: {

        },
        resolve: {
            plugins: [

            ],
            extensions: [".ts", ".tsx", ".js"]
        },
        devtool: "source-map",
        module: {
            rules: [
            ]
        },
        plugins: [
        ]
    };

    return config;
};

module.exports = getConfig;

I then have two projects each with their own webpack.config.ts

import webpack from "webpack";
import * as path from 'path';

const getConfig = require("../../webpack.config.base");

const appDir = __dirname;
const distDir  = path.resolve(__dirname, "../AppOne.Web/wwwroot");

const getConfigFactory = (env: any, args: any): webpack.Configuration => getConfig(appDir, distDir, env, args);

module.exports = getConfigFactory;

This all works absolutely fine. Here's a full example of this factory getConfig = () => {} style.

My problem is when I try to change to change

const getConfig = require("../../webpack.config.base");

To an es6 import. This is even provided as a suggestion by VS code.

enter image description here

When I apply this change I get

enter image description here

Here's my tsconfig.json I already have [allowSyntheticDefaultImports][5] enabled. Suggested here.

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "moduleResolution": "node",
    "jsx": "react",
    "experimentalDecorators": true,
    "lib": [
      "es2015",
      "dom"
    ],
    "target": "es5",
    "forceConsistentCasingInFileNames": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true,
    "noUnusedLocals": true,
    "typeRoots": [
      "./node_modules/@types/",
      "./types/"
    ],
    "baseUrl": ".",
    "paths": { }
  },
  "include": [
    "./src/**/*.ts",
    "./src/**/*.tsx"
  ]
}

But I added export default getConfig; anyway... and npm run build again. It still fails.

const getConfigFactory = (env: any, args: any): webpack.Configuration => getConfig(appDir, distDir, env, args);
                                                                                  ^
TypeError: webpack_config_base_1.default is not a function

My final attempt before smashing my head into the table was to change

import getConfig from "../../webpack.config.base";
import * as base from "../../webpack.config.base";

Delete the export default getConfig; in the webpack.config.base.ts and export the const getConfig directly as export const getConfig. But at that point what's the point of module.exports = getConfig. Not to mention it also doesn't bloody work (same issue as before)

const getConfigFactory = (env: any, args: any): webpack.Configuration => base.getConfig(appDir, distDir, env, args);
                                                                              ^
TypeError: base.getConfig is not a function

What am I missing here? Why can't I simply replace const getConfig = require("../../webpack.config.base"); with import getConfig from "../../webpack.config.base"

PS.

Here's my "scripts" for running this

    "build:appone": "webpack --mode=development --config ./src/AppOne.App/webpack.config.ts",
    "build:apptwo": "webpack --mode=development --config ./src/AppTwo.App/webpack.config.ts",
like image 332
Sigex Avatar asked Jan 25 '26 15:01

Sigex


1 Answers

What does work?

I configured webpack with TypeScript for multiple environments (e.g. development and production) with a common base configuration. To do so, I utilize:

  • Node.js (version 11.12.0)
  • TypeScript (version 3.5.3)
  • webpack (version 4.39.1)

Directory Structure

Suppose the following directory structure :

project/
├── ...
├── webpack.common.ts
├── webpack.development.ts
└── webpack.production.ts

Other directory structures (such as two nested projects as in your example) will work as well. Although, you may have to adapt the paths used in the examples below.

TypeScript Configuration

The base configuration of webpack, webpack.common.ts, looks as follows:

import webpack from 'webpack';

const configuration: webpack.Configuration = {
  // Your common webpack configuration...
};

export default configuration;

The two webpack configurations for different environments extend this base configuration using the npm package webpack-merge. You can find its typings for TypeScript in the npm package @types/webpack-merge. To stick with the initial example, the webpack configuration for the development environment looks as follows:

import webpack from 'webpack';
import merge from 'webpack-merge';
import commonWebpackConfiguration from './webpack.common';

const configuration: webpack.Configuration = merge(commonWebpackConfiguration, {
  mode: 'development'
  // Your `development`-specific configuration...
});

export default configuration;

Meanwhile, the webpack configuration for the production environment is no different to the one used for the development environment in terms of import/export syntax.

Imports and Exports

As you may have noticed, I use the ES6 import/export syntax:

export default configuration;

and

import commonWebpackConfiguration from './webpack.common';

Consequently, there is no CommonJS syntax for imports and exports (e.g. module.exports or require) involved which keeps the webpack configuration files clean and allows for a seamless workflow. This is actually the recommended way to work with webpack and TypeScript as per webpack's documentation.


Why does your configuration not work?

First Attempt

My problem is when I try to change

const getConfig = require("../../webpack.config.base");

to an es6 import. This is even provided as a suggestion by VS code.

module.exports does indeed not have a default export (i.e. the object module.exports does not have any property named default) as the error you observed stated.
Using ES6 default export (i.e. export default) actually creates an export named default just like for other named exports (e.g. bar in the following). This can be illustrated compiling the following TypeScript snippet:

const helloWorld = () => {
  console.log('Hello, world!');
};
const bar = 'foo';

export default helloWorld;
export { bar };

to JavaScript using tsc:

"use strict";
exports.__esModule = true;
var helloWorld = function () {
    console.log('Hello, world!');
};
var bar = 'foo';
exports.bar = bar;
exports["default"] = helloWorld;

Second Attempt

But I added export default getConfig; anyway... and npm run build again. It still fails.

In this case, adding allowSyntheticDefaultImports (which you already have) and esModuleInterop (which you miss) to your TypeScript configuration tsconfig.json both set to true would remedy that issue 1.

Third Attempt

My final attempt before smashing my head into the table was to change

import getConfig from "../../webpack.config.base";

to

import * as base from "../../webpack.config.base";

Delete the export default getConfig; in the webpack.config.base.ts and export the const getConfig directly as export const getConfig. But at that point what's the point of module.exports = getConfig.

Having

export const getConfig = () => {...};

and

module.exports = getConfig;

at the same time, will give you base === getConfig (as only module.exports = getConfig; takes effect). Consequently, calling base.getConfig gives undefined since your defined function getConfig does not have a property getConfig. Hence, "base.getConfig is not a function".

Import Syntax Interoperability

Why can't I simply replace const getConfig = require("../../webpack.config.base"); with import getConfig from "../../webpack.config.base"?

const getConfig = require("../../webpack.config.base");

is a CommonJS import while

import getConfig from "../../webpack.config.base";

is an ES6 import. They cannot be used interchangeable since they are inherently different. You should choose to work with one of them, but not both at the same time.

like image 129
Daniel Fürst Avatar answered Jan 28 '26 13:01

Daniel Fürst