webpack系列-pug实战

Pug(jade)

模板引擎。类似于JSP、FreeMarker之类Java相关的模板引擎。Pug(原称是jade)是Nodejs相关的模板引擎,可以和Express进行集成。

类似的Nodejs相关的模板引擎:ejsart-templatedoTHandlebarsMustache

模板引擎通常都是有后端搭配进行数据传输,但是有的时候只需要静态页面不需要后端时,类似Java的模板引擎就不好用了。可以在node环境下使用模板引擎可以生成静态页面后发布。

模板引擎的好处在于可以将重复的代码提出来进行模块化,可以进行灵活的嵌入、继承、扩展等,降低后期维护难度和代码修改工作量。

主要问题在于数据传值部分。

  • 可以将数据写在模板里定义常量进行数据取值,但是灵活性不高,如果不同的网站数据相同的样式,需要重新创建文件定义常量。
  • 可以将数据写在JSON文档中,可以进行单文件或多文件进行命令行编译。
  • 可以使用webpack进行打包,补充兼容、压缩和优化代码,但是难点在于webpack配置和pug配置,如特殊需要,可能需要编写插件或loader。

实例代码:https://github.com/Datura35422/pug-demo/tree/master/website

项目配置

package.json

{
  "name": "website",
  "version": "1.0.0",
  "description": "",
  "dependencies": {
    "jquery": "^3.6.0"
  },
  "devDependencies": {
    "@babel/core": "^7.13.10",
    "@babel/preset-env": "^7.13.10",
    "autoprefixer": "^10.2.5",
    "babel-loader": "^8.2.2",
    "clean-webpack-plugin": "^3.0.0",
    "copy-webpack-plugin": "^8.0.0",
    "css-loader": "^5.1.3",
    "css-mqpacker": "^7.0.0",
    "cssnano": "^4.1.10",
    "file-loader": "^6.2.0",
    "html-webpack-injector": "^1.1.4",
    "html-webpack-plugin": "^5.3.1",
    "mini-css-extract-plugin": "^1.3.9",
    "postcss-loader": "^5.2.0",
    "pug-loader": "^2.4.0",
    "url-loader": "^4.1.1",
    "webpack": "^5.26.3",
    "webpack-cli": "^4.5.0",
    "webpack-dev-server": "^3.11.2",
    "webpack-merge": "^5.7.3"
  },
  "scripts": {
    "build": "npx webpack --config ./build/webpack.build.conf.js",
    "debug": "node --inspect --inspect-brk webpack --config ./build/webpack.build.conf.js",
    "dev": "npx webpack-dev-server --open --config ./build/webpack.dev.conf.js"
  },
  "author": "",
  "license": "ISC",
  "browserslist": [
    "> 1%",
    "last 3 version"
  ]
}

webpack 配置

webpack.base.conf.js

// reference https://github.com/vedees/webpack-template-pug & https://github.com/vedees/webpack-template

const path = require('path');
const fs = require('fs');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackInjector = require('html-webpack-injector');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const dataJson = require('../src/data/home.json')

// Main const
const PATHS = {
  // Path to main app dir
  src: path.join(__dirname, '../src'),
  // Path to Output dir
  dist: path.join(__dirname, '../dist')
}

// Pages const for HtmlWebpackPlugin
const PAGES_DIR = `${PATHS.src}/pages/`
const PAGES = fs.readdirSync(PAGES_DIR).filter(fileName => fileName.endsWith('.pug'))

module.exports = {
  // BASE config
  externals: {
    paths: PATHS // webpack.dev.conf 中 devServer中会使用
  },
  entry: {
    index: `${PATHS.src}/js/index.js`,
    index_head: `${PATHS.src}/js/judegDevice.js`
  },
  output: {
    filename: `js/[name].[fullhash].js`,
    path: PATHS.dist,
    publicPath: './'
  },
  // 优化
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          name: 'vendors',
          test: /node_modules/,
          chunks: 'all',
          enforce: true
        }
      }
    }
  },
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({ // 压缩优化css
      filename: `css/[name].[fullhash].css`
    }),
    // 处理静态文件 将静态引入的文件复制到目标目录下 要与html中的文件引用一样 不然HtmlWebpackPlugin会编译出错
    new CopyWebpackPlugin({
      patterns: [
        {
          from: `src/imgs`,
          to: `imgs`
        },
        {
          from: `src/favicon.ico`,
          to:  `favicon.ico`
        }
      ]
    }),
    // 自动加载模块
    new webpack.ProvidePlugin({ // 其他js文件中使用jquery 进行自动导入
      $: 'jquery',
      jQuery: 'jquery',
      jquery: 'jquery'
    }),

    // Automatic creation any html pages (Don't forget to RERUN dev server)
    // see more: https://github.com/vedees/webpack-template/blob/master/README.md#create-another-html-files
    // best way to create pages: https://github.com/vedees/webpack-template/blob/master/README.md#third-method-best
    ...PAGES.map(page => {
      return new HtmlWebpackPlugin({
        minify: 'production', // 压缩
        template: `${PAGES_DIR}/${page}`,
        filename: `${page.replace(/\.pug/,'.html')}`,
        chunks: ['index', 'index_head'],
        data: dataJson
        // chunksConfig: {             // Added option
        //   async: ["index_head"],    
        //   defer: ["index"]
        // }
      })
    }),
    new HtmlWebpackInjector(), // js分开注入
  ],
  stats: {
    children: true
  },
  module: {
    rules: [
      {
        test: /\.pug$/,
        use: [
          {
            loader: 'pug-loader',
            options: { // 配置参考 pug api:https://pugjs.org/zh-cn/api/reference.html
              pretty: true, // 不压缩html
              self: true // 局部变量
            }
          }
        ]
      }, 
      {
        test: /\.js$/,
        exclude: '/node_modules/',
        use: ['babel-loader']
      }, 
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        use: [
          {
            loader: 'url-loader', // 处理js、css中出现的img
            options: {
              // 为css内的图片、文件等外部资源指定一个自定义的公共路径
              name(resourcePath) { // 替换css中的文件名
                console.log('resourcePath: ', resourcePath)
                let url = path.relative(`${PATHS.src}/imgs/`, path.dirname(resourcePath)) + '/[name].[ext]'
                url = url.replace('\\', '/')
                return url;
              },
              outputPath: 'imgs', // 输出目录, 如果配置好路径的话可以不用配置
              limit: 8190
            }
          }
        ],
        type: 'javascript/auto' // webpack v5
      }, 
      {
        test: /\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              // 将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
              publicPath: (resourcePath) => {
                return path.relative(path.dirname(resourcePath), `${PATHS.src}/`) + '/';
              }
            }
          },
          'css-loader',
          'postcss-loader'
        ]
      }
    ]
  }
};

webpack.dev.conf.js

const webpack =  require('webpack')
const { merge } = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')

const devWebpackConfig = merge(baseWebpackConfig, {
  // DEV config
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    contentBase: baseWebpackConfig.externals.paths.dist, // 且仅在使用静态资源的时候配置
    port: 8081,
    open: true,
    hot: true,
    hotOnly: true, // 热更新
    // historyApiFallback: true, // 响应html内容
    overlay: { // 编译器警告
      warnings: true,
      errors: true
    }
  },
  plugins: [
    new webpack.SourceMapDevToolPlugin({
      filename: '[file].map'
    })
  ]
})

module.exports = new Promise((resolve, reject) => {
  resolve(devWebpackConfig)
})

webpack.build.conf.js

const { merge } = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')

const buildWebpackConfig = merge(baseWebpackConfig, {
  // BUILD config
  mode: 'production',
  plugins: []
})

module.exports = new Promise((resolve, reject) => {
  resolve(buildWebpackConfig)
})

其他配置

postcss.config.js

// autoprefixer - https://github.com/postcss/autoprefixer
// css-mqpacker - https://github.com/hail2u/node-css-mqpacker
// cssnano      - https://github.com/hail2u/node-css-mqpacker

// npm install postcss-loader autoprefixer css-mqpacker cssnano --save-dev

module.exports = {
    plugins: [
        require('autoprefixer'),
        require('css-mqpacker'),
        require('cssnano')({
            preset: [
                'default', 
                {
                    discardComments: {
                        removeAll: true
                    }
                }
            ]
        })
    ]
}

.babelrc

{
    "presets": [
    	"@babel/preset-env"
    ]
}