如何从 Webpack 4 中的页眉/页脚(和 css/js 注入)生成静态页面的 html 模板(lodash 模板不起作用)?

Posted

技术标签:

【中文标题】如何从 Webpack 4 中的页眉/页脚(和 css/js 注入)生成静态页面的 html 模板(lodash 模板不起作用)?【英文标题】:How to generate html template for static page from header/footer (and css/js inject) in Webpack 4 (lodash template not working)? 【发布时间】:2020-08-06 05:28:59 【问题描述】:

我正在尝试使用 webpack 4 构建静态 html 页面。我正在使用 html-webpack-plugin。我想定义一些 header.htmlfooter.html 并稍后在所有 html 页面中导入它们。但我还希望我的输出 js 和 css 文件自动注入到这个页眉和页脚。像这样的:

header.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Some Title</title>
    <% for(var i=0; i < htmlWebpackPlugin.files.css.length; i++) %>
        <link type="text/css" rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[i] %>">
    <%  %>
</head>
<body>

footer.html:

    <% for(var i=0; i < htmlWebpackPlugin.files.js.length; i++) %>
        <script type="text/javascript" src="<%= htmlWebpackPlugin.files.js[i] %>"></script>
    <%  %>
</body>
</html>

index.hbs(或 index.html):

<%= _.template(require('./../includes/header.html'))() %>

    <div class="content">
        <img src="img/bg/desktop/bg-index-03.jpg"  />
        <div class="imgtest"></div>
    </div> <!-- .content -->

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

但我在 dist/index.html 中遇到了一些错误。这些行仍然相同,并且不会将页眉/页脚加载到 index.html:

<%= _.template(require('./../includes/header.html'))() %>
...
<%= _.template(require('./../includes/footer.html'))() %>

由于某种原因 lodash 不起作用...

更新:

我将src/html/views/index.hbs 更改为src/html/views/index.html (并在插件中)

new HtmlWebpackPlugin(
  template: './src/html/views/index.html',
...

在此处添加include 行:


    test: /\.html$/,
    include: path.resolve(__dirname, 'src/html/includes'),
    use: ['html-loader']
,

在页眉/页脚中,我删除了 lodash 代码 &lt;% ... %&gt;,只留下干净的 html - 所以它可以工作!在 index.html 中导入页眉/页脚,但没有 css/js。

如果我在 footer.html(或 header.html)中返回 lodash

<% for(var i=0; i < htmlWebpackPlugin.files.js.length; i++) %>
    <script type="text/javascript" src="<%= htmlWebpackPlugin.files.js[i] %>"></script>
<%  %>

我得到错误:

ERROR in Template execution failed: ReferenceError: htmlWebpackPlugin is not defined

ERROR in   ReferenceError: htmlWebpackPlugin is not defined

  - lodash.templateSources[2]:10 eval
    lodash.templateSources[2]:10:19

  - index.html:102 
    D:/.../src/html/views/index.html:102:110

  - index.html:104 ./node_modules/html-webpack-plugin/lib/loader.js!./src/html/views/index.html.module.exports
    D:/.../src/html/views/index.html:104:3

  - index.js:393 
    [project]/[html-webpack-plugin]/index.js:393:16

  - runMicrotasks

  - task_queues.js:93 processTicksAndRejections
    internal/process/task_queues.js:93:5

  - async Promise.all

为什么会这样?怎么了?如何使 lodash 在页眉/页脚中工作?

或者生成 html 模板的最佳方法是什么?

文件树:

dist
│   index.html
├───css
│       main.css
│       main.css.map
├───fonts
├───img
├───js
│       main.js
│       main.js.map
│       vendors~main.js
│       vendors~main.js.map
src
├───favicon
├───fonts
├───html
│   ├───includes
│   │       footer.html
│   │       header.html
│   └───views
│           index.hbs
├───img
├───js
│       index.js
├───scss
│       fonts.scss
│       icomoon.scss
│       style.scss
package.json
package-lock.json
webpack.config.js

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const  CleanWebpackPlugin  = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
const OptimizeCssAssetWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

const isDev = process.env.NODE_ENV === 'development';
const isProd = !isDev;

const optimization = () => 
  const config = 
    splitChunks: 
      chunks: 'all'
    
  
  if (isProd) 
    config.minimizer = [
      new OptimizeCssAssetWebpackPlugin(),
      new TerserWebpackPlugin()
    ]
  
  return config


const filename = ext => isDev ? `[name].$ext` : `[name].[hash].$ext`;

const cssLoaders = extra => 
  const loaders = [
    
      loader: MiniCssExtractPlugin.loader,
      options: 
        hmr: isDev,
        reloadAll: true
      ,
    ,
    
      loader: 'css-loader',
      options: 
        url: false
      
    
  ];
  if (extra) 
    loaders.push(extra)
  
  return loaders


const babelOptions = preset => 
  const opts = 
    presets: [
      '@babel/preset-env'
    ],
    plugins: [
      '@babel/plugin-proposal-class-properties'
    ]
  

  if (preset) 
    opts.presets.push(preset)
  

  return opts


module.exports = 
  mode: 'development',
  entry: [
    '@babel/polyfill',
    './src/js/index.js'
  ],
  output: 
    filename: 'js/' + filename('js'),
    path: path.resolve(__dirname, 'dist')
  ,
  devServer: 
    port: 4250,
    hot: isDev
  ,
  devtool: isDev ? 'source-map' : '',
  resolve: 
    //extensions: ['.js', '.json', '.png'],
    alias: 
      '@views': path.resolve(__dirname, 'src/html/views'),
      '@includes': path.resolve(__dirname, 'src/html/includes'),
      '@scss': path.resolve(__dirname, 'src/scss'),
      '@': path.resolve(__dirname, 'src'),
    
  ,
  optimization: optimization(),
  module: 
    rules: [
      
        test: /\.html$/,
        //include: path.resolve(__dirname, 'src/html/includes'),
        use: ['html-loader']
      ,
      
        test: /\.hbs$/,
        loader: 'handlebars-loader'
      ,
      
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 
          loader: 'babel-loader',
          options: babelOptions()
        
      ,
      
        test: /\.css$/,
        use: cssLoaders()
      ,
      
        test: /\.s[ac]ss$/,
        use: cssLoaders('sass-loader')
      ,
      
        test: /\.(png|jpg|svg|gif)$/,
        exclude: path.resolve(__dirname, 'src/fonts'),
        use: [
          
            loader: 'file-loader',
            options: 
              name: '[name].[ext]',
              outputPath: path.resolve(__dirname, 'dist/img')
            
          
        ]
      ,
      
        test: /\.(ttf|otf|svg|woff|woff2|eot)$/,
        exclude: path.resolve(__dirname, 'src/img'),
        use: [
          
            loader: 'file-loader',
            options: 
              name: '[name].[ext]',
              outputPath: path.resolve(__dirname, 'dist/fonts')
            
          
        ]
      ,
    ]
  ,
  plugins: [
    new HtmlWebpackPlugin(
      template: './src/html/views/index.hbs',
      minify: 
        collapseWhitespace: isProd
      ,
      inject: false
    ),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin(
      filename: 'css/' + filename('css'),
    ),
    new CopyWebpackPlugin([
      
        from: path.resolve(__dirname, 'src/favicon'),
        to: path.resolve(__dirname, 'dist')
      ,
      
        from: path.resolve(__dirname, 'src/fonts'),
        to: path.resolve(__dirname, 'dist/fonts')
      ,
      
        from: path.resolve(__dirname, 'src/img'),
        to: path.resolve(__dirname, 'dist/img')
      
    ])
  ]
;

package.json


  "name": "Name",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": 
    "dev": "cross-env NODE_ENV=development webpack --mode development",
    "build": "cross-env NODE_ENV=production webpack --mode production",
    "watch": "cross-env NODE_ENV=development webpack --mode development --watch",
    "start": "cross-env NODE_ENV=development webpack-dev-server --mode development --open"
  ,
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": 
    "@babel/core": "^7.9.0",
    "@babel/plugin-proposal-class-properties": "^7.8.3",
    "@babel/preset-env": "^7.9.5",
    "babel-loader": "^8.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "copy-webpack-plugin": "^5.1.1",
    "cross-env": "^7.0.2",
    "css-loader": "^3.5.2",
    "file-loader": "^6.0.0",
    "html-loader": "^1.1.0",
    "html-webpack-plugin": "^4.2.0",
    "mini-css-extract-plugin": "^0.9.0",
    "node-sass": "^4.13.1",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "raw-loader": "^4.0.1",
    "resolve-url-loader": "^3.1.1",
    "sass-loader": "^8.0.2",
    "style-loader": "^1.1.4",
    "terser-webpack-plugin": "^2.3.5",
    "webpack": "^4.42.1",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.10.3"
  ,
  "browserslist": "defaults",
  "dependencies": 
    "@babel/polyfill": "^7.8.7",
    "bootstrap": "^4.4.1",
    "handlebars": "^4.7.6",
    "handlebars-loader": "^1.7.1",
    "jquery": "^3.5.0",
    "popper.js": "^1.16.1"
  

【问题讨论】:

【参考方案1】:

解决了!

我在HtmlWebpackPlugin 中将inject 更改为true

我还添加了函数 generateHtmlPlugins() 以自动将所有 html 文件添加到来自 ./src/html/views 的插件。

src/html/includes/header.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Some Title</title>
</head>
<body>
<header>header</header>

src/html/includes/footer.html:

    <footer>footer</footer>
</body>
</html>

src/html/views/index.html:

<%= _.template(require('./../includes/header-main.html'))() %>
    <div class="content">
        <img src="img/bg/desktop/bg-index-03.jpg"  />
        <div class="imgtest"></div>
    </div>
<%= _.template(require('./../includes/footer.html'))() %>

dist/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
    <title>Some Title</title>
<link href="css/main.43f2075150009f972ed4.css" rel="stylesheet"></head>
<body>
<header>header</header>
    <div class="content">
        <img src="img/bg/desktop/bg-index-03.jpg"  >
        <div class="imgtest"></div>
    </div>
    <footer>footer</footer>
<script src="js/vendors~main.43f2075150009f972ed4.js"></script><script src="js/main.43f2075150009f972ed4.js"></script></body>
</html>

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const  CleanWebpackPlugin  = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
const OptimizeCssAssetWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const fs = require('fs');

const isDev = process.env.NODE_ENV === 'development';
const isProd = !isDev;

function generateHtmlPlugins(templateDir) 
  const templateFiles = fs.readdirSync(path.resolve(__dirname, templateDir));
  return templateFiles.map(item => 
    const parts = item.split('.');
    const name = parts[0];
    const extension = parts[1];
    return new HtmlWebpackPlugin(
      filename: `$name.html`,
      template: path.resolve(__dirname, `$templateDir/$name.$extension`),
      minify: 
        collapseWhitespace: isProd
      ,
      inject: true,
    )
  )


const htmlPlugins = generateHtmlPlugins('./src/html/views')

const optimization = () => 
  const config = 
    splitChunks: 
      chunks: 'all'
    
  
  if (isProd) 
    config.minimizer = [
      new OptimizeCssAssetWebpackPlugin(),
      new TerserWebpackPlugin()
    ]
  
  return config


const filename = ext => isDev ? `[name].$ext` : `[name].[hash].$ext`;

const cssLoaders = extra => 
  const loaders = [
    
      loader: MiniCssExtractPlugin.loader,
      options: 
        hmr: isDev,
        reloadAll: true
      ,
    ,
    
      loader: 'css-loader',
      options: 
        url: false
      
    
  ];
  if (extra) 
    loaders.push(extra)
  
  return loaders


const babelOptions = preset => 
  const opts = 
    presets: [
      '@babel/preset-env'
    ],
    plugins: [
      '@babel/plugin-proposal-class-properties'
    ]
  

  if (preset) 
    opts.presets.push(preset)
  

  return opts


module.exports = 
  mode: 'development',
  entry: [
    '@babel/polyfill',
    './src/js/index.js'
  ],
  output: 
    filename: 'js/' + filename('js'),
    path: path.resolve(__dirname, 'dist')
  ,
  devServer: 
    port: 4250,
    hot: isDev
  ,
  devtool: isDev ? 'source-map' : '',
  resolve: 
    //extensions: ['.js', '.json', '.png'],
    alias: 
      '@views': path.resolve(__dirname, 'src/html/views'),
      '@includes': path.resolve(__dirname, 'src/html/includes'),
      '@scss': path.resolve(__dirname, 'src/scss'),
      '@': path.resolve(__dirname, 'src'),
    
  ,
  optimization: optimization(),
  module: 
    rules: [
      
        test: /\.html$/,
        include: path.resolve(__dirname, 'src/html/includes'),
        loader: 'html-loader',
        options: 
          minimize: false,
        
      ,
      
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 
          loader: 'babel-loader',
          options: babelOptions()
        
      ,
      
        test: /\.css$/,
        use: cssLoaders()
      ,
      
        test: /\.s[ac]ss$/,
        use: cssLoaders('sass-loader')
      ,
      
        test: /\.(png|jpg|svg|gif)$/,
        exclude: path.resolve(__dirname, 'src/fonts'),
        use: [
          
            loader: 'file-loader',
            options: 
              name: '[name].[ext]',
              outputPath: path.resolve(__dirname, 'dist/img')
            
          
        ]
      ,
      
        test: /\.(ttf|otf|svg|woff|woff2|eot)$/,
        exclude: path.resolve(__dirname, 'src/img'),
        use: [
          
            loader: 'file-loader',
            options: 
              name: '[name].[ext]',
              outputPath: path.resolve(__dirname, 'dist/fonts')
            
          
        ]
      ,
    ]
  ,
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin(
      filename: 'css/' + filename('css'),
    ),
    new CopyWebpackPlugin([
      
        from: path.resolve(__dirname, 'src/favicon'),
        to: path.resolve(__dirname, 'dist')
      ,
      
        from: path.resolve(__dirname, 'src/fonts'),
        to: path.resolve(__dirname, 'dist/fonts')
      ,
      
        from: path.resolve(__dirname, 'src/img'),
        to: path.resolve(__dirname, 'dist/img')
      
    ])
  ].concat(htmlPlugins)
;

【讨论】:

以上是关于如何从 Webpack 4 中的页眉/页脚(和 css/js 注入)生成静态页面的 html 模板(lodash 模板不起作用)?的主要内容,如果未能解决你的问题,请参考以下文章

C#/VB.NET 如何在 Word 文档中添加页眉和页脚

如何从数据框中删除页眉和页脚?

excel表格怎么设置页眉页脚 xcel表格如何设置页眉页脚

需要从本地 iframe 中的 src url 中删除页眉和页脚

Facebook 如何在加载不同页面时保持页眉和页脚固定?

如何从pdf文件中查找页眉页脚