webpack4 + ejs 构建多页应用

目录结构

├─build   webpack配置目录
│   ├─plugins.js
│   ├─rules.js
│   ├─transfromAssets.js  //简单的一个插件,处理路径问题
│   └─webpack.config.js
└─src
    ├─components  ejs公用组件目录
    ├─css
    ├─js
    ├─index.ejs 模板文件
    └─about.ejs

源码地址:github

使用ejs模板

  1. 安装 ejs-loader、html-loader
  2. 在 src根节点创建ejs文件,作为html的模板
  3. 使用html-webpack-plugin引入ejs模板

index.ejs 需要向header.ejs传入htmlWebpackPlugin,否则html-webpack-plugin配置的title可能会不起作用

<%= require(\'./components/header.ejs\')({path:\'index\',htmlWebpackPlugin}) %>
<div >
  内容
</div>
<%= require(\'./components/footer.ejs\')() %>

header.ejs

<head>
  <meta charset="utf-8">
  <title><%= htmlWebpackPlugin.options.title %></title>
</head>

webpack plugins,因为是多个页面,所以通过for循环插入HtmlWebpackPlugin

const HtmlWebpackPlugin = require(\'html-webpack-plugin\');
const plugins = [];
...
nav.forEach(value => {
  plugins.push(
    new HtmlWebpackPlugin({
      filename: `${value.path}.html`,
      template: path.resolve(__dirname, \'../src\', `${value.path}.ejs`),
      inject: true,
      chunks: [\'common\', value.path],
      favicon: \'./src/assets/img/favicon.ico\',
      title: \'title\',
      minify: {
        collapseWhitespace: true
      }
    })
  )
})
...

处理css

  1. 使用了less,所以需要安装loader:css-loader、less-loader
  2. 添加浏览器前缀,兼容不同版本浏览器,需要安装postcss-loader、autoprefixer
  3. 需要压缩生成的css文件,安装插件:optimize-css-assets-webpack-plugin、cssnano
  4. 将css分离成css文件,安装mini-css-extract-plugin

处理js

安装下babel-loader、@babel/core、@babel/preset-env,处理es6的语法。

webpack配置

plugins.js

const HtmlWebpackPlugin = require(\'html-webpack-plugin\');
const MiniCssExtractPlugin = require(\'mini-css-extract-plugin\');
const optimizeCss = require(\'optimize-css-assets-webpack-plugin\');
const TransfromAssets = require(\'./transfromAssets\');
const path = require(\'path\');
const nav = require(`../src/data.js`).nav;
const plugins = [];

nav.forEach(value => {
  plugins.push(
    new HtmlWebpackPlugin({
      filename: `${value.path}.html`,
      template: path.resolve(__dirname, \'../src\', `${value.path}.ejs`),
      inject: true,
      chunks: [\'common\', value.path],
      favicon: \'./src/assets/img/favicon.ico\',
      title: \'title\',
      minify: {
        collapseWhitespace: true
      }
    })
  )
})
const otherPlugins = [
  new MiniCssExtractPlugin({
    filename: \'[name].[hash:8].css\',
    chunkFilename: \'[id].css\',
  }),
  new optimizeCss({
    assetNameRegExp: /\.css$/g,
    cssProcessor: require(\'cssnano\'),
    cssProcessorOptions: {
      discardComments: {
        removeAll: true
      }
    },
    canPrint: true
  }),
  new TransfromAssets()
];
plugins.splice(nav.length, 0, ...otherPlugins);
module.exports = plugins;

rules.js

const MiniCssExtractPlugin = require(\'mini-css-extract-plugin\');

module.exports = [{
    test: /\.(c|le)ss$/,
    use: [
      MiniCssExtractPlugin.loader,
      \'css-loader\',
      {
        loader: "postcss-loader",
        options: {
          plugins: [
            require("autoprefixer")
          ]
        }
      },
      \'less-loader\'
    ]
  },
  {
    test: /\.js$/, //js文件加载器
    exclude: /node_modules/,
    use: {
      loader: \'babel-loader\',
      options: {
        presets: [\'@babel/preset-env\']
      }
    }
  },
  {
    test: /\.html$/,
    use: [{
      loader: \'html-loader\',
      options: {
        interpolate: true,
        minimize: false
      }
    }]
  }, {
    test: /\.ejs/,
    use: [\'ejs-loader\'],
  }
]

transfromAssets.js 一个插件,作用:

  1. 将common.js删掉(common.js引入了公用的css,css有用但js无用所以就删啦)
  2. 将js、css存放至单独的目录,并将html里路径指向正确的路径
function TransfromAssets(options) {};//也可以是一个类
TransfromAssets.prototype.apply = function(compiler) {
  compiler.plugin(\'emit\', function(compilation, callback) {
    for (var filename in compilation.assets) {
      if (/common.*js$/.test(filename)) {
        delete compilation.assets[filename];
        continue;
      }
      if (/.*[js|css]$/.test(filename)) {
        let type = /.*js$/.test(filename) ? \'js\' : \'css\';
        let source = compilation.assets[filename].source();
        let size = compilation.assets[filename].size();
        compilation.assets[`${type}/${filename}`] = {
          source: () => source,
          size: () => size
        }
        delete compilation.assets[filename];
      }
      if (/html$/.test(filename)) {
        let source = compilation.assets[filename].source();
        source = source.replace(/\<script.*?<\/script>/ig, value => ~value.indexOf(\'common\') ? \'\' : value);
        source = source.replace(/href=\"\S+?.css\"/ig, value => value.slice(0, 6) + \'css/\' + value.slice(6));
        source = source.replace(/src=\".*?.js\"/ig, value => value.slice(0, 5) + \'js/\' + value.slice(5));
        compilation.assets[filename].source = () => source;
      }
    }
    callback();
  })
};
module.exports = TransfromAssets;

webpack.config.js

const path = require(\'path\');
const plugins = require(\'./plugins\');
const rules = require(\'./rules\');

module.exports = {
  entry: {
    common: \'@src/js/common.js\',
    index: \'@src/js/index.js\',
    detail: \'@src/js/list.js\',
  },
  output: {
    path: path.resolve(__dirname, \'../dist/\'),
    filename: "[name].[hash:8].js"
  },
  devServer: {
    inline: true,
    historyApiFallback: true,
  },
  resolve: {
    alias: {
      \'@\': path.join(__dirname, \'..\'),
      \'@src\': path.join(__dirname, \'..\', \'src\')
    }
  },
  module: {
    rules
  },
  plugins
}