1

Is it possible to render template files (such as Pug or Handlebars) dynamically at runtime using Webpack and Express?

My issue is when loading my root page (index.pug), the html loads however no assets are loading.

Example:

app
.set('views', path.join(__dirname, 'views'))
.set('view engine', 'pug')
.use('/', function(req, res) {      
    res.render('index', {some: 'param'})
})

If I remove the '/' route handler, the page loads with all of the assets just fine.

Client webpack.config.js file:

module.exports = {
entry: {
    main: ['webpack-hot-middleware/client?path=/__webpack_hmr&timeout=60000', './index.js', './css/main.css']
},
output: {
    path: path.join(__dirname, 'dist'),
    publicPath: '/',
    filename: '[name].js'
},
mode: 'development',
target: 'web',
devtool: '#source-map',
module: {
    rules: [
        {
            test: /\.pug$/,
            use: ['html-loader?attrs=false', 'pug-html-loader']
        }
    ]
},
plugins: [
    new HtmlWebPackPlugin({
        template: './views/index.pug',
        filename: "./index.html",
        excludeChunks: [ 'app' ]
    })
]
}

Server webpack.server.config.js:

module.exports = (env, argv) => {
return ({
    entry: {
        app: 'app.js',
    },
    output: {
        path: path.join(__dirname, 'dist'),
        publicPath: '/',
        filename: '[name].js'
    },
    target: 'node',
    node: {
        // Need this when working with express, otherwise the build fails
        __dirname: false,   // if you don't put this is, __dirname
        __filename: false,  // and __filename return blank or /
    },
    externals: [nodeExternals()], // Need this to avoid error when working with Express
    module: {
        rules: [
            {
                // Transpiles ES6-8 into ES5
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader"
                }
            }
        ]
    }
})
}
Graham
  • 7,431
  • 18
  • 59
  • 84
McLovin
  • 1,455
  • 3
  • 19
  • 37
  • Were you able to resolve this? I'm stuck with the same issue and starting to wonder whether I should rip out webpack altogether. I understand that the reason for the error is because you're using pre-webpacked template in your render(), that excludes all other assets that webpack would bundle in, but how to point to the webpacked version of the file (especially with hot reloading on - that doesn't actually generate a file on disk) is beyond me. – Alexander Tsepkov Jun 15 '19 at 02:37
  • Unfortunately no, I never resolved this. I actually ended up pivoting to React – McLovin Jun 17 '19 at 00:13

1 Answers1

2

You can make it work in 2 easy steps (based on the code above).

  1. In server change the views path to where your built template is going to be. In the example above it's going to be inside the dist folder
    app
    .set("views", "./dist");
    .set('view engine', 'pug')
    .use('/', function(req, res) {      
        res.render('index', {some: 'param'})
    })

At this point, after we run a build script and start the server we would get a message that there's no view in dist folder, which is correct because engine is looking for a template and all we have are html files

  1. In webpack.config.js in HtmlWebPackPlugin we need to leave a filename as a template
    plugins: [
        new HtmlWebPackPlugin({
            template: './views/index.pug',
            filename: "./index.pug",
            excludeChunks: [ 'app' ]
        })
    ]

And that's it. Now template engine will find index.pug in dist with injected style and script and generate html from it

Note: In production it's important to set a path for static files, otherwise you won't get your css displayed even though the link tags were injected correctly

app.use(express.static(__dirname));

The above solution worked for me when I run into the same problem, but with the ejs template. I couldn't find the answer to it anywhere, so hopefully, it will save someone hours of frustration.

mo.smulko
  • 21
  • 3