Speed Up Webpack for Aurelia 1

I’d like to share a portion of my blog article here. For the full version, including trials with Vite, tsgo, and more, please check out the full post.


Among many JavaScript frameworks, I found Aurelia to be the best. It is simple to write, yet powerful and stable. However, in the case of Aurelia 1, the compilation process can be slow with good old Webpack. I tried several tools—including Vite, Rspack, and tsgo—and saw a 9.6x speed improvement using Webpack itself.

Before

yarn start  # (webpack + tsc)

# webpack 5.99.9 compiled successfully in 11500 ms

After

yarn start  # (webpack + cache + tsc + ForkTsCheckerWebpackPlugin)

# 1st run: webpack 5.99.9 compiled successfully in 6177 ms
# 2nd run: webpack 5.99.9 compiled successfully in 1195 ms  (9.6x faster!)

How?

I just added file caching and ForkTsCheckerWebpackPlugin to my webpack.config.js and achieved a 9.6x speedup on the second run. Because I had upgraded from Webpack 4, I wasn’t aware of the caching.

ForkTsCheckerWebpackPlugin forces ts-loader (TypeScript compiler frontend) to set transpileOnly: true to speed up transpile and executes type checking in a separate thread.

yarn add -D fork-ts-checker-webpack-plugin
- webpack.config.js

import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';  //***
...

export default ({ production }, { analyze, hmr, port, host }) => ({
  cache: {
    type: 'filesystem',
  },

  // ...other config such as ts-loader

  plugins: [

    new ForkTsCheckerWebpackPlugin({
        typescript: {
          diagnosticOptions: {
            semantic: true,
            syntactic: true,
          },
        },
    }),

Also, don’t forget to enable Hot Module Reloading (HMR) in aurelia_project/aurelia.json:

- aurelia_project/aurelia.json

"platform": {
  "hmr": true
}

Other trials are here.

3 Likes

Without:
2025-06-27 10:31:02: webpack 5.96.1 compiled successfully in 36794 ms

With, first run:
2025-06-27 10:37:11: webpack 5.96.1 compiled with 192 warnings in 19009 ms

So, unfortunately not just a drop-in. But the change already is impressive!

With, second run:
2025-06-27 10:38:37: webpack 5.96.1 compiled with 192 warnings in 8079 ms

WARNING in ./<filepath>.ts 84:163-180
export '<ClassName>' (imported as '<ClassName>') was not found in '~/<filepath>'
 (module has no exports)

I’ve tried several options, but without avail. Really unfortunate, because it sure looks promising!

I suspect it’s just a configuration issue, so I’ve raised a question issue on the plugin page:

Hopefully there is a solution, if so I’ll added here!

Hi mroeling, have you confirmed that you have this in the filepath.ts?

export class YourClass...

Yes, first thing I checked ofc.
Without export the Error is different too:

TS2459: Module '"~/..."' declares '<CLassName>' locally, but it is not exported.

I see.

I’ve experienced that removing the file cache fixed some issues.

rm -r node_modules/.cache

Your Webpack seems a bit older than mine. What if you upgrade to the latest one?

This says that removing the browser key from package.json fixed the ‘module has no exports’ error.

Here are suggestions by Gemini 2.5 Pro. Please ask your AI for further.


The issue you’re facing is a classic case of a configuration mismatch between how Webpack bundles modules and how a separate type-checking process resolves them.

The problem stems from the interaction between fork-ts-checker-webpack-plugin, TypeScript path aliases (~/*), and the modern moduleResolution: "Bundler" setting in the tsconfig.json.

Here’s the breakdown and the solution you can provide.


The Root Cause

When you add ForkTsCheckerWebpackPlugin, you effectively split one job into two separate processes:

  1. Transpilation: ts-loader (now with transpileOnly: true) quickly converts TypeScript to JavaScript for Webpack. It uses tsconfig-paths-webpack-plugin to correctly resolve your ~/* aliases during the bundling process.
  2. Type Checking: ForkTsCheckerWebpackPlugin runs TypeScript in a separate process to check for type errors. This process reads your tsconfig.json directly, but it does not use tsconfig-paths-webpack-plugin.

The conflict arises from the "moduleResolution": "Bundler" setting. This modern setting tells TypeScript to defer to the bundler’s (Webpack’s) logic for resolving paths. While this works great for the transpilation step, the separate type-checking process doesn’t have the full context of the Webpack bundler, so it fails to correctly resolve the ~/* path alias. This leads to the misleading "export not found (module has no exports)" warning, which in this case really means “I couldn’t find the module at this path.”


The Solution

The most reliable solution is to change the module resolution strategy in tsconfig.json to one that the standalone TypeScript process can handle without relying on a bundler.

Change this line in tsconfig.json:

JSON// "moduleResolution": "Bundler", "moduleResolution": "node",

By changing "moduleResolution" from "Bundler" to "node", you are using TypeScript’s classic and highly compatible Node.js resolution algorithm. Both Webpack (with tsconfig-paths-webpack-plugin) and the separate fork-ts-checker-webpack-plugin process understand this algorithm perfectly, resolving the path aliases correctly in both contexts.


Updating Your Article

To prevent this issue for other readers, I recommend adding a small note to your article.

Suggested addition:

A Note on TypeScript Configuration

If you use TypeScript path aliases in your tsconfig.json (e.g., "paths": { "~/*": ["./*"] }), you may encounter "export not found" warnings after adding ForkTsCheckerWebpackPlugin.

This is often caused by the "moduleResolution": "Bundler" setting. To resolve this, change it to "node" in your tsconfig.json:

  "compilerOptions": {
    "moduleResolution": "node"
  }
}

This ensures that both Webpack and the separate type-checker can correctly resolve your module paths.


Sticking with moduleResolution: "Bundler" is a common requirement, especially in modern setups. The issue can still be resolved by making the path alias configuration more explicit for both Webpack and the TypeScript checker.

Here are the alternative solutions.


Solution 1: Synchronize Webpack’s resolve.alias (Recommended)

This is the most robust solution. Instead of relying on a plugin to translate tsconfig.json paths for Webpack, you define the alias explicitly in both your webpack.config.js and tsconfig.json. This creates two clear, independent “sources of truth” that happen to align—one for the bundler and one for TypeScript’s tools (like the VS Code language server and the fork-ts-checker-webpack-plugin).

1. Keep the paths in tsconfig.json

This is crucial for your IDE and for fork-ts-checker-webpack-plugin. This part doesn’t change.

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "~/*": ["./*"]
    }
    // ... other options
  }
}

2. Add resolve.alias to webpack.config.js

This tells Webpack directly how to handle the ~ alias. You will also need to import Node’s path module at the top of your webpack config.

// webpack.config.js
const path = require('path'); // <-- Make sure you have this
// ... other imports

module.exports = ({ production }, { analyze, hmr, port, host }) => ({
  // ...
  resolve: {
    extensions: ['.ts', '.js'],
    modules: [path.resolve(__dirname, 'src'), 'node_modules'],
    alias: {
      '~': path.resolve(__dirname, 'src') // <-- Add this alias
    }
  },
  plugins: [
    // You no longer need TsconfigPathsPlugin if this is your only alias!
    // new TsconfigPathsPlugin(), // <-- You can likely remove this
    new ForkTsCheckerWebpackPlugin({
      // ...
    })
  ]
  // ...
});

By defining the alias in both places, Webpack has its own clear rule, and the fork-ts-checker has its own clear rule from the tsconfig.json. Since they both point to the same location (src), the inconsistency is resolved.


Solution 2: Adjust TypeScript Module Settings

The moduleResolution: "Bundler" setting works in tandem with other flags that control module handling. While the user has esModuleInterop enabled, another flag could help reduce ambiguity for the type checker.

Try adding verbatimModuleSyntax: true to tsconfig.json

This newer TypeScript setting is the successor to importsNotUsedAsValues. It enforces a stricter, more explicit style for import statements, which can help static analysis tools like fork-ts-checker-webpack-plugin better understand the dependency graph.

// tsconfig.json
{
  "compilerOptions": {
    "moduleResolution": "Bundler",
    "verbatimModuleSyntax": true, // <-- Add this
    "esModuleInterop": true
    // ...
  }
}

After adding this, you may need to fix some imports in your code, for example by explicitly adding import type { ... } for types that are only used in type annotations. This cleanup often resolves underlying module ambiguity that trips up the checker.


Summary for the User

If you must keep "moduleResolution": "Bundler", the best approach is to synchronize your aliases by defining them in both webpack.config.js’s resolve.alias and tsconfig.json’s paths. This makes the configuration explicit for all tools in your chain and typically removes the need for tsconfig-paths-webpack-plugin, simplifying your build.

Wow! That’s quite an answer, much appreciated!

I’ll definitely look into it again. I’ll update my findings in this ticket.

Thanks!