0

I decided to shift from ejs to pug as a template engine in my webpack projects. And what I need is pre-compiled static output (either in dev or in prod mode). Plus, I need to pass data to templates through the compilation process, and I use custom options of htmlWebpackPlugin for it. The project structure is as follows:

root
|--package.json
|--webpack.configs // .dev.js, build.js
|--data.json
|--layout.pug
|--app
|--|--index.js
|--|--views
|--|--|--index.pug
|--|--|--includes
|--|--|--|--nav.pug
|--|--|--|--footer.pug
|--assets
|--|--(styles, images...)

The first way I found and tried is to pass data to the entry point (compile locals at index.js and insert them to the page via innerHTML =). This way is absolutely not suitable for me as I need 100% pre-compiled static output without runtime HTML insertions. And then the fun begins...

For example, I need to create navigation. I have the schema of navigation as follows:

module.exports.nav = [
        {
          text: 'Home',
          href: '/'
        },
        {
          text: 'Offers',
          href: '/offers.html'
        },
        {
          text: 'Subnav Parent Test',
          isSubnavTrigger: true
        },
        {
          text: 'Subnav Item',
          href: '/test.html',
          isSubnavItem: true,
          subnavParent: 'Subnav Parent Test'
        }
      ]

and pug partial for this app/views/includes/nav.pug:

nav.navbar(role="navigation", ariaLabel="navigation")
  .container
    .navbar-brand
      a.navbar-item(href="/")
        img(src="/assets/images/logo.png", alt=title)
    .navbar-menu(id="navbar-menu")
      .navbar-start
      .navbar-end
        for link, index in nav
          //- TODO: isActive
          if !link.isSubnavTrigger && !link.isSubnavItem
            a(href=link.href, class="navbar-item")= link.text
          else if link.isSubnavTrigger
            .navbar-item.has-dropdown.is-hoverable
              a.navbar-link= link.text
              .navbar-dropdown
                - var parent = link.text
                - var dd = nav.filter(_item => _item.subnavParent === 
parent)
                each dd_link in dd
                  a.navbar-item(href=dd_link.href)= dd_link.text

that partial is included in common layout layout.pug:

- var { title, nav } = htmlWebpackPlugin.options;
<!DOCTYPE html>
html(lang="en")
  head
    title= title + 'Pug Webpack Test'
    block metas
  body
    header
      include ./app/views/includes/nav.pug

    main
      block content

    footer  
      include ./app/views/includes/footer.pug

with the hope that app/views/includes/nav.pug will interpolate nav variable.

So I've discovered 2 cases: use module -> rules -> loader and inline loader to HTMLWebpackPlugin.

The 1st. webpack.config.js settings:

module.exports = {
  module: {
    rules: [
      //...
      {
        test: /\.pug$/,
        use: ['file-loader?name=[path][name].html', 'pug-html-loader?pretty&exports=false']
      }
      //...
    ],
  },
  plugins: [
    //...
    new HtmlWebpackPlugin({
      template: './layout.pug',
      filename: 'index.html',
      title: 'siteTitle',
      nav: nav  // required somewhere at the top of the file
    })
  ]
}

With this case I fail to pass options from htmlWebpackPlugin into templates. As mentioned in layout.pug, I try to destruct title and nav fields from options and get compilation error: Cannot read property 'options' of undefined.

The 2nd case. webpack.config.js:

module.exports = {
  module: {
    rules: [
      //...
      // Removed PUG loaders
      //...
    ],
  },
  plugins: [
    //...
    new HtmlWebpackPlugin({
      template: '!!pug-loader!./layout.pug', // <- inlined loader
      filename: 'index.html',
      title: 'siteTitle',
      nav: nav  // required somewhere at the top of the file
    })
  ]
}
// by the way, in case of EJS I've been using exactly this approach
// and it worked out pretty well with partials

This approach perfectly compiles layout.pug with options taken from htmlWebpackPlugin, until it does not contain included partials. And I need to pass my nav to corresponding partial and to render nav from there. And partial start to throw errors into compilation as if it doesn't understand pug and needs loader:

  ERROR in ./app/views/includes/nav.pug 13:12
    Module parse failed: Unexpected token (13:12)
    You may need an appropriate loader to handle this file type.
    |       .navbar-start
    |       .navbar-end
    >         for link, index in nav
    |           //- TODO: isActive
    |           if !link.isSubnavTrigger && !link.isSubnavItem
     @ ./app/views/index.pug (c:/_npmg/node_modules/pug-loader!./app/views/index.pug) 4:514-543

I'm totally in mess. I do not want to draw back to ejs, but if I will not find an answer, it seems that I will have to.

Thanks forward.

Graham
  • 7,431
  • 18
  • 59
  • 84
Alex Naidovich
  • 188
  • 4
  • 15

2 Answers2

3

SOLVED.

The answer consists of 2 conclusions:

  1. Use appropriate loader.

    The only loader that fits to my needs is pug-loader (not pug-html-loader and not chain of multiple loaders). Reference is here, but in 2 words:

    pug-loader: returns a function

    pug-html-loader: returns a string, so it's static and no options can be pass through

    So, the final webpack.config.js settings regarding this are:

module.exports = {
  module: {
    rules: [
      //...
      test: /\.pug$/,
      use: [
        {loader: 'pug-loader'}
      ]
      //...
    ],
  },
  plugins: [
    //...
    new HtmlWebpackPlugin({
      template: './layout.pug', // <- NOT inlined loader
      filename: 'index.html',
      title: 'siteTitle',
      nav: nav  // required somewhere at the top of the file
    })
  ]
}
  1. Partials do not inherit variables from layout that partial have been required from.

    So, as I want to use data from htmlWebpackPlugin.options, I have to declare variable for this data not in common layout file, but in partial itself:

// app/views/partials/nav.pug
- var { nav } = htmlWebpackPlugin.options

nav.navbar(... and the other code you've seen above)

In case of someone face this problem, this is 100% working approach.

Alex Naidovich
  • 188
  • 4
  • 15
2

I want to notify OP about his solution but i can't comment.

HtmlWebpackPlugin and pug-loader can work well together. Instead of using :

// webpack.config file
new HtmlWebpackPlugin({
      template: './layout.pug',
      filename: 'index.html',
      myContent: 'loremp ipsum'
    })

// app/views/partials/nav.pug
- var { myContent } = htmlWebpackPlugin.options
p= myContent

you can use the following syntax :

// webpack.config file
new HtmlWebpackPlugin({
   template: './layout.pug',
   filename: 'index.html',
   templateParameters: {
       myContent: 'loremp ipsum'   
   }
})

// app/views/partials/nav.pug
p= myContent

It works with all pug engine features and we get more readable templates.

serrulien
  • 695
  • 4
  • 14