Webpack实战-管理多个单页应用

引入问题

上一节3-9为单页应用生成HTML中只生成了一个 HTML 文件,但在实际应用中一个完整的系统不会把所有的功能都做到一个网页中,因为这会导致这个网页性能不佳。
实际的做法是按照功能模块划分成多个单页应用,每个单页应用生成一个 HTML 文件。并且随着业务的发展更多的单页应用可能会逐渐被加入到项目中去。

虽然上一节已经解决了自动化生成 HTML 的痛点,但是手动去管理多个单页应用的生成也是一件麻烦的事情。
来继续改造上一节的例子,要求如下:

  • 项目目前共有2个单页应用组成,一个是主页 index.html,一个是用户登入页 login.html
  • 多个单页应用之间会有公共的代码部分,需要把这些公共的部分抽离出来,放到单独的文件中去以防止重复加载。例如多个页面都使用一套 CSS 样式,都采用了 React 框架,这些公共的部分需要抽离到单独的文件中;
  • 随着业务的发展后面可能会不断的加入新的单页应用,但是每次新加入单页应用不能去改动构建相关的代码。

在开始前先来看看该应用最终发布到线上的代码。

login.html 文件内容:

<html>
<head>
<meta charset="UTF-8">
<!--从多个页面中抽离出的公共 CSS 代码-->
<link rel="stylesheet" href="common_7cc98ad0.css">
<!--只有这个页面需要的 CSS 代码-->
<link rel="stylesheet" href="login_e31e214b.css">
<!--注入 google_analytics 中的 JS 代码-->
<script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create','UA-XXXXX-Y','auto');
ga('send','pageview');</script>
<!--异步加载 Disqus 评论-->
<script async="" src="https://dive-into-webpack.disqus.com/embed.js"></script>
</head>
<body>
<div id="app"></div>
<!--从多个页面中抽离出的公共 JavaScript 代码-->
<script src="common_a1d9142f.js"></script>
<!--只有这个页面需要的 JavaScript 代码-->
<script src="login_f926c4e6.js"></script>
<!--Disqus 评论容器-->
<div id="disqus_thread"></div>
</body>
</html>

构建出的目录结构为:

dist
├── common_029086ff.js
├── common_7cc98ad0.css
├── index.html
├── index_04c08fbf.css
├── index_b3d3761c.js
├── login.html
├── login_0a3feca9.js
└── login_e31e214b.css

如果按照上节的思路,可能需要为每个单页应用配置一段如下代码:

new WebPlugin({
  template: './template.html',// HTML 模版文件所在的文件路径
  filename: 'login.html' // 输出的 HTML 的文件名称
})

并且把页面对应的入口加入到 enrty 配置项中,就像这样:

entry: {
  index: './pages/index/index.js',// 页面 index.html 的入口文件
  login: './pages/login/index.js',// 页面 login.html 的入口文件
}

当有新页面加入时就需要修改 Webpack 配置文件,新插入一段以上代码,这会导致构建代码难以维护而且易错。

解决方案

上一节中的 web-webpack-plugin 插件也内置了解决这个问题的方法,上一节中只使用了它的 WebPlugin
这节将使用它的 AutoWebPlugin 来解决以上问题,使用方法非常简单,下面来教你具体如何使用。

项目源码目录结构如下:

├── pages
│ ├── index
│ │ ├── index.css // 该页面单独需要的 CSS 样式
│ │ └── index.js // 该页面的入口文件
│ └── login
│     ├── index.css
│     └── index.js
├── common.css // 所有页面都需要的公共 CSS 样式
├── google_analytics.js
├── template.html
└── webpack.config.js

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

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

Webpack 配置文件修改如下:

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 方法可以获取到所有由 autoWebPlugin 生成的入口配置
  entry: autoWebPlugin.entry({
    // 这里可以加入你额外需要的 Chunk 入口
  }),plugins: [
    autoWebPlugin,],};
以上配置文件为了重点展示出本文侧重修改的部分,省略了部分和上一节一致的代码,完整代码可以参照上一节或者下载本项目完整代码。

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

{
  "index":["./pages/index/index.js","./common.css"],"login":["./pages/login/index.js","./common.css"]
}

但这些事情 AutoWebPlugin 都会自动为你完成,你不用操心,明白大致原理即可。

template.html 模版文件如下:

<html>
<head>
  <meta charset="UTF-8">
  <!--在这注入该页面所依赖但没有手动导入的 CSS-->
  <!--STYLE-->
  <!--注入 google_analytics 中的 JS 代码-->
  <script src="./google_analytics.js?_inline"></script>
  <!--异步加载 Disqus 评论-->
  <script src="https://dive-into-webpack.disqus.com/embed.js" async></script>
</head>
<body>
<div id="app"></div>
<!--在这注入该页面所依赖但没有手动导入的 JavaScript-->
<!--SCRIPT-->
<!--Disqus 评论容器-->
<div id="disqus_thread"></div>
</body>
</html>

注意到模版文件中出现了2个重要的新关键字 <!--STYLE--><!--SCRIPT-->,它们是什么意思呢?

由于这个模版文件被当作项目中所有单页应用的模版,就不能再像上一节中直接写 Chunk 的名称去引入资源,因为需要被注入到当前页面的 Chunk 名称是不定的,每个单页应用都会有自己的名称。
<!--STYLE--><!--SCRIPT--> 的作用在于保证该页面所依赖的资源都会被注入到生成的 HTML 模版里去。

web-webpack-plugin 能分析出每个页面依赖哪些资源,例如对于 login.html 来说,插件可以确定该页面依赖以下资源:

  • 所有页面都依赖的公共 CSS 代码 common.css
  • 所有页面都依赖的公共 JavaScrip 代码 common.js
  • 只有这个页面依赖的 CSS 代码 login.css
  • 只有这个页面依赖的 JavaScrip 代码 login.css

由于模版文件 template.html 里没有指出引入这些依赖资源的 HTML 语句,插件会自动将没有手动导入但页面依赖的资源按照不同类型注入到 <!--STYLE--><!--SCRIPT--> 所在的位置。

  • CSS 类型的文件注入到 <!--STYLE--> 所在的位置,如果 <!--STYLE--> 不存在就注入到 HTML HEAD 标签的最后;
  • JavaScrip 类型的文件注入到 <!--SCRIPT--> 所在的位置,如果 <!--SCRIPT--> 不存在就注入到 HTML BODY 标签的最后。

如果后续有新的页面需要开发,只需要在 pages 目录下新建一个目录,目录名称取为输出 HTML 文件的名称,目录下放这个页面相关的代码即可,无需改动构建代码。

由于 AutoWebPlugin 是间接的通过上一节提到的 WebPlugin 实现的,WebPlugin 支持的功能 AutoWebPlugin 都支持。

AutoWebPlugin 插件还支持一些其它更高级的用法,详情可以访问该项目主页阅读文档。

本实例 提供项目完整代码

《深入浅出Webpack》全书在线阅读链接

阅读原文

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom