Running Aurelia CLI Typescript (Webpack) app in ExpressJS webpack-dev-middleware

195 Views Asked by At

I'm trying to get the following setup to work:

  • Standard Aurelia "Hello World" Typescript app (created by CLI, build by Webpack)
  • Served by an ExpressJS app using webpack-dev-middleware
  • Everything combined in one project in order to be able to share code between the Aurelia front-end app and the ExpressJS back-end app

I stared out with the "Hello World" Aurelia app, moved the code into a src/frontend folder, renamed the webpack.config.js to webpack-frontend.config.js and the npm scripts also to start-frontend and so on. After these modifications, the Aurelia app still works great.

Then I added a basic Express JS back-end application with its own webpack.config.js, tsconfig.js and of course npm start script, which also works fine.

Finally I tried to get the Express app to launch the front-end app through a webpack-dev-middleware like so:

    private applyWebpackDevMiddleware(server: Express) {
        if (Environment.isLocal()) {
            const config = require('../../webpack-frontend.config.js');
            const compiler = require('webpack')(config);

            const webpackDevMiddleware = require('webpack-dev-middleware');
            server.use(
                webpackDevMiddleware(compiler, {
                    hot: true,
                    publicPath: config.output.publicPath,
                    compress: true,
                    host: 'localhost',
                    port: Environment.getPort()
                })
            );

            const webpackHotMiddleware = require('webpack-hot-middleware');
            server.use(webpackHotMiddleware(compiler));
        }
    }

This is exactly the same webpack config file that works just fine when used directly with the webpack command. However like so, I'm getting the following error message:

(node:15111) UnhandledPromiseRejectionWarning: WebpackOptionsValidationError: Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
 - configuration should be an object.
    at webpack (/Users/will/Desktop/test/aurelia-express/node_modules/webpack/lib/webpack.js:31:9)
    at ExpressServer.applyWebpackDevMiddleware (/Users/will/Desktop/test/aurelia-express/src/backend/ExpressServer.ts:141:48)
    at ExpressServer.setup (/Users/will/Desktop/test/aurelia-express/src/backend/ExpressServer.ts:41:14)
    at Function.createApplication (/Users/will/Desktop/test/aurelia-express/src/backend/Application.ts:18:29)
    at Object.<anonymous> (/Users/will/Desktop/test/aurelia-express/src/backend/index.ts:12:13)
    at Module._compile (internal/modules/cjs/loader.js:816:30)
    at Module.m._compile (/Users/will/Desktop/test/aurelia-express/node_modules/ts-node/src/index.ts:439:23)
    at Module._extensions..js (internal/modules/cjs/loader.js:827:10)
    at Object.require.extensions.(anonymous function) [as .ts] (/Users/will/Desktop/test/aurelia-express/node_modules/ts-node/src/index.ts:442:12)
    at Module.load (internal/modules/cjs/loader.js:685:32)
    at Function.Module._load (internal/modules/cjs/loader.js:620:12)
    at Function.Module.runMain (internal/modules/cjs/loader.js:877:12)
    at Object.<anonymous> (/Users/will/Desktop/test/aurelia-express/node_modules/ts-node/src/bin.ts:157:12)
    at Module._compile (internal/modules/cjs/loader.js:816:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:827:10)
    at Module.load (internal/modules/cjs/loader.js:685:32)
(node:15111) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)

Can anyone either give me some pointers, why the same config file works fine when used directly in webpack, but fails when used through the webpack-dev-middleware? After all, it's in the same project with the same node_modules, and therefore with the same webpack version, right?

Or does anyone have a working setup as described above that they could share? Thanks!!

2

There are 2 best solutions below

0
On

While winwiz1 did provide some very valuable points (e.g. TypeScript path mapping) I would sill like to present the solution to the problem, which is actually quite simple:

au new creates a "Hello World" project with a webpack.config.js which exports a function. However when you want to use webpack with Express webapck-dev-middleware, an object is required. This object is returned by the function in the webpack.config.js file. So all you need to do is to require the file and then call that function:

function applyWebpackDevMiddleware(server: Express) {
    server.use(express.static('static'));

    // Import the function from the webpack config and call the function
    // "false" is the "production" flag
    const config = require('../../webpack.config.js')(false);
    // then us the config object in webpack
    const compiler = require('webpack')(config)

    const webpackDevMiddleware = require('webpack-dev-middleware')
    server.use(webpackDevMiddleware(compiler, {
        writeToDisk: true,
        hot: true,
        publicPath: config.output.publicPath,
        compress: true,
        host: 'localhost',
        port: Environment.getInstance().port
    }))

    const webpackHotMiddleware = require('webpack-hot-middleware')
    server.use(webpackHotMiddleware(compiler))
}
1
On

Served by an ExpressJS app using webpack-dev-middleware

The combination of ExpressJS and webpack-dev-middleware is called webpack-dev-server. It's used very widely in development: 3.4 million repositories depend on it. Its installation rate is 7 million per week. Replacing it in development with something that you have developed/assembled would only make sense if you are either not satisfied with its functionality or think you can provide a better implementation. Otherwise just use it in development and don't use it in production because as its name suggests webpack-dev-server is meant to be used for development only.

Everything combined in one project in order to be able to share code between the Aurelia front-end app and the ExpressJS back-end app

Sharing between frontend and backend makes perfect sense, merging two projects into one doesn't. Frontend and backend have different run-time and development dependencies, you want to deploy backend in production which is lean and mean to take less space, be more secure etc. Therefore it is better to keep the two projects separate and achieve sharing by:

  • copying frontend build artifacts (html files, script bundles) from frontend to backend in production to let Express serve the artifacts from disk.
  • proxying in development to take advantage of webpack-dev-server Live Reloading etc.
  • when using TypeScript (which you do), sharing code between projects can be done via path mapping.

As a practical example have a look at this project though it doesn't use Aurelia. I'm the author.