webpack多页面实践

引言

对于一般的项目,特别是移动端项目,我们所熟知的都是webpack+vue,webpack+react等打包成一个单页应用,包括我在github上所能找到的很多都是将我们所有的模块打包成一个大大的js包(甚至css也打包进去)然后在html中引入。

众所周知,单页应用依赖模块很大时会有很大的性能问题,当我们的web应用越来越大时,我们不可能会将所有的功能都放在同一个网页上,这时一般会做的是按功能划分模块,分成多个单页应用,这样做会有利于后期功能放入扩展调整。

要构建多页应用,我们应该要考虑以下的问题:

  1. 单页应用应该会有公共的代码部分,我们要将这些公用的部分抽离出来,以免重复引用加载。
  2. 随着业务的发展,后面可能会不断加入新的单页应用,但是在加入新应用时都不
    能改动构建相关的代码。 
  3.  

先看看一般单页应用的配置:

const path = require('path');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const { WebPlugin } = require('web-webpack-plugin');

module.exports = {
  entry: {
    app: './main.js'// Chunk app 的 JS 执行入口文件
  },
  output: {
    filename: '[name]_[chunkhash:8].js',// 给输出的文件名称加上 hash 值
    path: path.resolve(__dirname, './dist'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
        // 排除 node_modules 目录下的文件,node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换
        exclude: path.resolve(__dirname, 'node_modules'),
      },
      {
        test: /\.css/,// 增加对 CSS 文件的支持
        // 提取出 Chunk 中的 CSS 代码到单独的文件中
        use: ExtractTextPlugin.extract({
          use: ['css-loader?minimize'] // 压缩 CSS 代码
        }),
      },
    ]
  },
  plugins: [
    // 使用WebPlugin,一个 WebPlugin 对应一个 HTML 文件
    new WebPlugin({
      template: './template.html', // HTML 模版文件所在的文件路径
      filename: 'index.html' // 输出的 HTML 的文件名称
    }),
    new ExtractTextPlugin({
      filename: `[name]_[contenthash:8].css`,// 给输出的 CSS 文件名称加上 hash 值
    }),
    new DefinePlugin({
      // 定义 NODE_ENV 环境变量为 production 去除 react 代码中的开发时才需要的部分
      'process.env': {
        NODE_ENV: JSON.stringify('production')
      }
    }),
    // 压缩输出的 JS 代码
    new UglifyJsPlugin({
      // 最紧凑的输出
      beautify: false,
      // 删除所有的注释
      comments: false,
     //忽略无关紧要的...
    }),
  ],
};
复制代码

这里最主要的是WebPlugin这个插件,能够让我们实现自动化输出html文件并引入打包后的模块,虽然这样已经对我们打包应用提供了很大的便利,但对于多个页面的管理依然是个麻烦的问题。如果我们要增加一个页面我们需要new一个webPlugin实例并在entry入口增加配置项。

解决方案

这里用AutoWebPlugin来构建我们的多页应用,项目源码的目录结构如下:

 从目录结构中可以看出以下几点要求:

  •  所有单页应用的代码都需要放到一个 目录下,例如都放在 pages 目录下;
  • 一个单页应用对应一个单独的文件夹,例如最后生成的 index.html 相关的代码都在 index 目录下, login.html 同理:
  • 每个单页应用的目录下都有一个 index.js 文件作为入口执行文件。AutoWebP lugin 强制性地规定了项目部分的目录结构,从实战经验来看,这是一种优雅的目录规范,合理地拆分了代码,又能让新人快速看懂项目的结构,方便日后维护。 

webpack配置文件如下:

const path = require('path');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const { AutoWebPlugin } = require('web-webpack-plugin');

// 使用AutoWebPlugin,自动寻找 pages 目录下的所有目录,把每一个目录看成一个单页应用
const autoWebPlugin = new AutoWebPlugin('pages', {
  template: './template.html', // HTML 模版文件所在的文件路径
  postEntrys: ['./common.css'],// 所有页面都依赖这份通用的 CSS 样式文件
  // 提取出所有页面公共的代码
  commonsChunk: {
    name: 'common',// 提取出公共代码 Chunk 的名称
  },
});

module.exports = {
  // AutoWebPlugin 会找为寻找到的所有单页应用,生成对应的入口配置,
  // autoWebPlugin.entry 方法可以获取到生成入口配置
  entry: autoWebPlugin.entry({
    // 这里可以加入你额外需要的 Chunk 入口
  }),
  output: {
    filename: '[name]_[chunkhash:8].js',// 给输出的文件名称加上 hash 值
    path: path.resolve(__dirname, './dist'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
        // 排除 node_modules 目录下的文件,node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换
        exclude: path.resolve(__dirname, 'node_modules'),
      },
      {
        test: /\.css/,// 增加对 CSS 文件的支持
        // 提取出 Chunk 中的 CSS 代码到单独的文件中
        use: ExtractTextPlugin.extract({
          use: ['css-loader?minimize'] // 压缩 CSS 代码
        }),
      },
    ]
  },
  plugins: [
    autoWebPlugin,
    new ExtractTextPlugin({
      filename: `[name]_[contenthash:8].css`,// 给输出的 CSS 文件名称加上 hash 值
    }),
    new DefinePlugin({
      // 定义 NODE_ENV 环境变量为 production 去除 react 代码中的开发时才需要的部分
      'process.env': {
        NODE_ENV: JSON.stringify('production')
      }
    }),
    // 压缩输出的 JS 代码
    new UglifyJsPlugin({
      // 最紧凑的输出
      beautify: false,
      // 删除所有的注释
      comments: false,
     //忽略无关紧要的...

    }),
  ],
};

复制代码

AutoWebPlugin 会找出 pages 目录下的两个文件夹 index 和 login ,将这两个文件夹看作两个单页应用,并且分别为每个单页应用生成一个 Chunk 配置和 WebPlugin 配置 。每个单页应用的 Chunk 名称等同于文件夹的名称,也就是说 autoWebPlugin.entry()方法返回的内容其实是:  

{  "index:["./pages/index/index.js", "./common.css"], 
     "login": ["./pages/login/index.js", "./common.css"] 
}复制代码
template.html 模板文件如下:

<html>
<head>
  <meta charset="UTF-8">
  <!--该页面所依赖的其它剩下的 CSS 注入的地方-->
  <!--STYLE-->
  <!--已忽略无关代码-->
</head>
<body>
<div id="app"></div>
<!--该页面所依赖的其它剩下的 JavaScript 注入的地方-->
<!--SCRIPT-->
 <!--已忽略无关代码-->
</body>
</html>


复制代码

注意到在模板文件中出现了两个重要的新关键字<!--STYLE--><!--SCRIPT-->,

它们是什么意思呢?  

由于该模板文件被当作项目中所有单页应用的模板,因为需要被注入当前页面的 Chunk 名称是不固定的,每个单页应用都会有自己的名称。<!--STYLE--><!--SCRIPT-->的作用在于保证该页面所依赖的资源都会被注入生成的 HTML 模板里。 

 web-webpack-plugin 能分析出每个页面依赖哪些资源,例如对于 login.html 来说,

该插件可以确定该页面依赖以下资源:
  • 所有页面都依赖的公共 css 代码 common. css;
  • 所有页面都依赖的公共 JavaScript 代码 common. js;
  • 只有这个页面依赖的 css 代码 login.css;
  • 只有这个页面依赖的 JavaScript 代码 login .css 。
由于在模板文件 template.html 里没有指出引入这些依赖资源的 HTML 语句,所以插件会自动将没有手动导入但页面依赖的资源按照不同的类型注入 <!--STYLE--><!--SCRIPT-->所在的位置。
  • 将 css 类型的文件注入<!--STYLE-->所在的位置,如果<!--STYLE-->不存在,就注入 HTML HEAD 标签的最后 。
  • 将 JavaScript 类型的文件注入<!--SCRIPT-->所在的位置,如果<<!--SCRIPT-->不存在,就注入 HTML BODY 标签的最后 。
如果后续有新的页面需要开发 ,就只需在 pages 目录下新建一个目录,该目录名为所
输出 HTML 文件的名称,在目录下放这个页面相关的代码即可,无须改动构建代码。  

npm run build后打包的代码如下:


login.html:

<html>
<head>
  <meta charset="UTF-8">
  <!--该页面所依赖的其它剩下的 CSS 注入的地方-->
  <link rel="stylesheet" href="common_7cc98ad0.css">
<link rel="stylesheet" href="login_e31e214b.css">
  <!--已忽略无关代码-->

</head>
<body>
<div id="app"></div>
<!--该页面所依赖的其它剩下的 JavaScript 注入的地方-->
<script src="common_bdac69cb.js"></script>
<script src="login_0a3feca9.js"></script>
 <!--已忽略无关代码-->

</body>
</html>
复制代码

参考阅读:


转载于:https://juejin.im/post/5c346285e51d45520a7667cb

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值