17

I'm trying to create a setup with static HTML partials using the HTML Webpack Plugin, but running into some errors. This is my current config:

webpack.config.js

const webpack = require('webpack');
const path = require('path');
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssets = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

    let config = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, './public'),
    filename: 'app.js'
  },
  module: {
    loaders: [{
        test: /\.html$/,
        loader: 'html-loader'
    }],
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
      {
        test: /\.scss$/,
        use: ['css-hot-loader'].concat(ExtractTextWebpackPlugin.extract({
          fallback: 'style-loader',
          use: ['css-loader', 'sass-loader', 'postcss-loader'],
        })),
      },
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        loaders: ['file-loader?context=src/assets/images/&name=images/[path][name].[ext]', {
          loader: 'image-webpack-loader',
          query: {
            mozjpeg: {
              progressive: true,
            },
            gifsicle: {
              interlaced: false,
            },
            optipng: {
              optimizationLevel: 4,
            },
            pngquant: {
              quality: '75-90',
              speed: 3,
            },
          },
        }],
        exclude: /node_modules/,
        include: __dirname,
      },
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
        template: './src/template.html.ejs'
    }),
    new ExtractTextWebpackPlugin('main.css')
  ],
  devServer: {
    contentBase: path.resolve(__dirname, './public'),
    historyApiFallback: true,
    inline: true,
    open: true
  },
  devtool: 'eval-source-map'
}

module.exports = config;

if (process.env.NODE_ENV === 'production') {
  module.exports.plugins.push(
    new webpack.optimize.UglifyJsPlugin(),
    new OptimizeCSSAssets()
  );
}

template.html.ejs (located under ./src)

<%=require('./header.html')%>
  <body>
    testing schmesting
  </body>
  <%=require('./footer.html')%>
</html>

(footer.html and header.html are located under ./src)

Edit: Updated the code, still issues:

"ERROR in Error: Child compilation failed: Module parse failed: Unexpected token (1:0) You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (1:0) Module parse failed: Unexpected token (1:2) You may need an appropriate loader to handle this file type."

Staffan Estberg
  • 6,795
  • 16
  • 71
  • 107

1 Answers1

13

EDIT

(EDIT: 2017-12-20 move "loaders" to "rules")

By default, "html-webpack-plugin" parses the template as "underscore" (also called lodash) template, your "template.html" is nothing wrong, the error is caused by webpack failed to resolve 'html-loader' for your "template.html" <%= require('html-loader!./footer.html') %>, so you need to install "html-loader" for webpack, and configure it:

In command line:

npm install html-loader

And configure it for webpack, edit webpack.config.js:

...
module: {
    rules: [
     // ... 
        {
            test: /\.html$/, // tells webpack to use this loader for all ".html" files
            loader: 'html-loader'
        }
    ]
}

By now you can run "webpack" you'll see no error, BUT the generated "index.html" is not you expected, because your template file has ".html" extension, webpack now use "html-loader" to load "template.html" instead of default "lodash loader", to solve this you can rename "template.html" to "template.html.ejs" (or any other extension) to make "html-webpack-plugin" fallback. Besides there is a little bit more change on "template.html", remove "html-loader!" from it:

<%= require('./footer.html') %>

now it should work.


EDIT Post my code for reference :

/src/template.html.ejs

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>test</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <h1>template</h1>
        <%=require('./footer.html')%>
    </body>
</html>

/src/footer.html

<footer>this is a footer</footer>

/webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

let config = {
    entry: {
        index: './src/js/index'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    module: {
        rules: [
            {
                test: /\.html$/,
                loader: 'html-loader'
            }
        ],
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/template.html.ejs'
        })
    ]
}


module.exports = config;

/package.json

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "html-loader": "^0.5.1",
    "html-webpack-plugin": "^2.30.1",
    "webpack": "^3.10.0"
  }
}

/src/js/index.js

console.log("A test page!");

environment:

  • webpack 3.10.0
  • npm 5.6.0

content of "/dist/index.html" after run webpack:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>test</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <h1>template</h1>
        <footer>this is a footer</footer>
    <script type="text/javascript" src="index.js"></script></body>
</html>

As you can see content of "footer.html" is correctly inserted.


OLD ANSWER

Approach 1: Using "es6" template

  1. Install "html-loader" by npm install html-loader
  2. Add "html-loader" to your "webpack.config.js" for loading files with ".html" extension, like:
module: {
        rules: [{
            test: /\.html$/,
            loader: 'html-loader'
        }],
    }
  1. Add interpolate flag to enable interpolation syntax for ES6 template strings, like so:
plugins: [
        new HtmlWebpackPlugin({
            template: '!!html-loader?interpolate!src/template.html'
        })
    ]
  1. Modify your template.html to match ES6 template:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    template
  </body>
  ${require('./footer.html')}
</html>
  1. Run webpack, it'll work

Approach 2: Using "underscore" template

Follow "approach 1" step 1 and 2, and then:

  • Rename "template.html" to "template.html.ejs"
  • Change template: './src/template.html' to template: './src/template.html.ejs' in "webpack.config.js"
  • Run webpack
kite.js.org
  • 1,599
  • 10
  • 11
  • Thanks! I tried adding this but there seems to be some error with point 3 (adding the interpolate). Are you sure I should be calling for HtmlWebPack Plugin here and not a reference to Html Loader? – Staffan Estberg Dec 17 '17 at 14:16
  • This is the error I get in the console when running Webpack (or npm run start): ERROR in ./src/footer.html Module parse failed: Unexpected token (1:7) You may need an appropriate loader to handle this file type. | footer test | @ ./node_modules/html-loader?interpolate!./src/template.html 1:161-185 – Staffan Estberg Dec 17 '17 at 14:21
  • Please post your newest "template.html" and "footer.html" here – kite.js.org Dec 18 '17 at 00:52
  • I believe you should run the install command with the `--save` option so that the dependency is saved in the modules file – jonny Dec 18 '17 at 03:17
  • I think `--save` option just update `dependencies` section of `package.json`, it's not a necessary option if you just want to test something, dependencies is installed automatically. And this option seems be removed from "npm ver 5.5 +" (not sure since which version), updates `package.json` becomes a default action. https://stackoverflow.com/questions/19578796/what-is-the-save-option-for-npm-install – kite.js.org Dec 18 '17 at 03:30
  • 1
    Quote from above page >>> As of npm 5.0.0, installed modules are added as a dependency by default, so the --save option is no longer used. The other save options still exist and are listed in the documentation for npm install – kite.js.org Dec 18 '17 at 03:38
  • I'm using npm 5.5.1 so guess no need for that. I copied and pasted the contents of your tempate.html sample into mine, as for the footer its just a text string "footer test" with no markup. – Staffan Estberg Dec 18 '17 at 05:28
  • 1
    @Staffan Estberg post updated, with my code for reference – kite.js.org Dec 18 '17 at 07:05
  • Thanks, I tried updating the code but I still run into compile errors, similar to the ones before. "You may need an appropriate loader to handle this file type." It sounds like the plugin hasnt been installed but it is. Any idea? " SyntaxError: Unexpected token (1:0) Module parse failed: Unexpected token (1:2) You may need an appropriate loader to handle this file type." – Staffan Estberg Dec 18 '17 at 12:49
  • Please edit your question and add minimal "template.html.ejs", "footer.html" and "webpack.config.js". – kite.js.org Dec 18 '17 at 13:34
  • I've added the full webpack.config.js file contents now. The header.html and footer.html just contains a tiny markup – Staffan Estberg Dec 19 '17 at 15:45
  • Sorry man, my mistake, you should remove "loaders" section and move `{ test: /\.html$/, loader: 'html-loader' }` to your "rules" section, see https://github.com/andywer/webpack-blocks/issues/111 – kite.js.org Dec 20 '17 at 08:03
  • It works now! Thank you so much :) Now I just need to figure out a way of using several templates and generating their partials on the fly... – Staffan Estberg Dec 20 '17 at 09:34
  • Can I add partial inside another partial? I tried but I only see as a string, like <%= require('./snippets/country-selector.html') %> or <%= require('../snippets/country-selector.html') %> – levipadre Apr 18 '18 at 12:34
  • 1
    @levipadre You can use `interpolate` flag to enable interpolation syntax for ES6 template strings in "html-loader", rules of `webpack.config.js` like this: `rules: [ { test: /\.html$/, loader: [{ loader: 'html-loader', options: { interpolate: true } }] } ],`, and then use ES6 template to require a html file `${ require('../snippets/country-selector.html') }`. Good luck! – kite.js.org Apr 19 '18 at 06:01
  • Thanks, @kite.js.org! Perfect. You helped a lot. It's not crucial, but do you think I can pass a parameter to partial? – levipadre Apr 19 '18 at 08:39
  • @levipadre sorry my friend, I haven't found a way to pass parameters by a query string like `require('snippets.html?key=value')`, but for an alternative way, you can use environment values, for example: `${ require( process.env.NODE_ENV === 'production' ? './snippets-production.html' : './snippets-testing.html') }`, and then run `NODE_ENV='production' webpack -p` you'll get './snippets-production.html' included, and vice versa – kite.js.org Apr 22 '18 at 14:36
  • Thanks again, @kite.js.org. That won't really help, but it's good to know I can do something like this. – levipadre Apr 23 '18 at 08:23
  • This answer ended hours of misery. +1 – sol Sep 04 '18 at 05:32