当前快应用的项目中,支持加载其它JS文件(通过:require('./foo.js')),然后通过webpack工具处理依赖,最终完成页面JS的构建,其中页面JS包含了引入的所有JS内容;
本文讨论的主要是:webpack打包时,能够将依赖JS不合入到页面JS中,而是在rpk的ZIP压缩文件中单独存在,然后运行时,页面在需要的时候加载该JS文件;

  1. 背景描述

当前如果每个页面都引入一个共同的JS文件,如:foo.js;那么toolkit工具打包时,会将每个foo.js都打包到页面JS代码中;

这样带来以下几个问题,造成页面渲染变慢:

1) 每个页面JS都包含了foo.js,使得JS代码重复,导致rpk包体积增大,从而运行时下载rpk时间变长;

2) 每个页面JS都包含各自的foo.js,运行时JS引擎编译代码字符串为AST和JS对象,用时变长;

3) 由于JS代码存在多个地方,导致每个页面引入的foo都是独立的JS对象,而不是:同一个JS内存对象;

面对上面的问题,我们提供一种新的规范来解决:编译时支持JS单独构建,并在运行时调用加载

2. 规范的方案拆解

针对上面所述,规范实现的工作主要拆解到两大模块中:

1) 编译时:页面中用到的公共JS文件;在构建时,生成单独的JS文件;保持rpk中只有一份该JS文件的代码;

2) 运行时:提供动态加载JS文件的API:页面中首次加载时,则从内存/磁盘中同步读取并执行;第二次加载时,选择复用内存中之前的JS对象或者重新执行;

上面划分看上去实现简单,但围绕一些拆解出来的子任务,其实思路很多,需要权衡利弊:

1) 编译时的单独JS构建方案;

2) 当前编译时用的toolkit使用的是webpack4;如果使用其它工具(如:rollupjs),如何持续兼容;

3) 动态加载JS文件时,需要考虑:模块化,缓存,页面/APP上下文,时间成本,向后兼容,可扩展能力等的问题;

比如:模块化是否要放在运行时;当页面销毁后如果保证页面里的接口调用也跟着销毁;

接下来,围绕各项子任务分别进行考虑;

3. 任务1:编译时的单独JS构建方案

目前市面上主流的构建方案分为两类:语法型,配置型;其中webpack4中的splitChunkPlugin应用就属于配置型;

3.1 语法型

这种指的是,快应用平台运行时直接给开发者暴露一个 $app_evaluate$ 的函数,开发者在自己的ux文件或者js文件中,通过`const foo = $app_evaluate$('./foo.js')`方式引入JS依赖;

当工具toolkit编译时,检测到这样的函数API的JS,就不再打包到页面JS中;

优点:

1) 开发者可以灵活改写,让某个JS既可以打包到某个页面中,又可以独立存在;

2) toolkit编译时无需开发者配置,简单场景下方便易用;

缺点:

1) 这种方式对单个JS引用,很容易替换不完全,造成重复打包;

2) 编译工具需要自行构建依赖分析的能力,将相对路径的引入转换为绝对路径;不仅无法复用webpack本身的依赖分析,而且需要增加依赖分析的逻辑和管理;实现起来增加工作量和难度;

3) 对开发者来说,有认知成本,与现有的require, import语法相比,不够贴切自然;

3.2 配置型

这种指的是,开发者通过给某个JS文件增加标识,标识为独立打包;接着在ux与js中,如果通过require或者import引入的,那么toolkit在编译时,根据标识,就不再打包到页面JS的代码中,而是单独创建一个JS文件;

比如:page1,page2等都引用了 foo.js文件,foo.js中声明:`/** quickapp:standalone */` 标识为该文件不打包到页面JS中;

优点:

1) 开发者只需要在JS文件中声明一下即可,或者采用其它配置形式(如:在manifest.json或者quickapp.config.js的配置文件中声明);

缺点:

1) 如果JS文件很多,可能每个希望独立打包的JS都需要声明;

2) 编译时webpack分析出依赖关系后,需要替换代码中的`require`关键字为`$app_evaluate$`,避免被webpack引入;

当然,配置型的表现形式也有很多:

1) 与文件所在路径放在一起,同时更新:

在JS文件中,使用标记 `/** quickapp:standalone */` 类似语法,配置每个JS文件单独打包;

2) 在manifest.json中声明:

在manifest.json文件中,配置JS文件单独打包,通过resource属性去声明单独打包的JS文件的路径;

3) 在quickapp.config.js中声明:

`quickapp.config.js`是快应用项目下的一个配置文件,代表项目将如何构建;通过在配置文件中声明,以确定哪些文件单独创建;

由于JS单独打包的功能仅仅只是一个编译时的工作,不应该与manifest这种运行时检查的文件混合在一起,因此优于上一条;

以上三种类似Java中的Annotation与XML声明,各有优缺:

前者直接分散,和代码一起,不必担心JS文件路径变化时,引起后者配置路径无效的问题;

后者直观方便,统一性强,但是与JS文件代码分离较远,容易发生路径找不到;

3.3 总结

其实对开发者来说,他关注的是:如何以最小的认知成本,来达到rpk最小的体积与最快的页面加载;

所以,最佳的方式是:什么配置都不需要就能实现,对开发者无感知;

其次是简单的一条选择项或一条配置就能完成;

语法型有较大的缺陷、难度、认知,就不再考虑了;

上面说的语法型、配置型是站在开发者的角度上说的;如果站在内部实现的角度来说,可能部分属于语法型了,因为涉及到对`require('foo.js')`替换为`$app_evaluate$('foo.js')`的更改;

4. 任务2:各页面加载同一个JS文件时,其JS对象是否应该共用

下文的`evaluate`指的是:编译JS文件的字符串代码 转换为 JS对象;(通过:eval,new Function等形式)

共用指的是:各页面都依赖同一个JS文件时,那么这个JS文件在evaluate后对应的JS对象,是否应该被各页面共用?

如果共用,意味着:各页面之间访问的是:同一个JS对象;页面之间对该模块可以一起更新并取值;

如果不共用,意味着:各页面之间访问的是:不同的JS对象;页面之间的该模块操作互不干扰影响;

4.1 总结

经过讨论,我们认为:各页面之间不要共用,利大于弊;

因为快应用是一个APP的概念,页面与页面之间应该保持很强的独立性,页面之间的数据与状态更新应该通过专有的固定通道来通信;

否则的话,开发者容易滥用,带来页面之间公用模块的互相操作影响,造成:耦合性强,状态不稳定,通信机制泛滥,监控不到位;

与浏览器中运行的SPA模式、NodeJS中模块共用的方式相比,快应用选择了不一样的设计思路,最主要的目的就是:保证页面的强独立性;

5. 任务3:如果JS模块不共用,那么页面之间隔离的方式怎么实现?

5.1 方式1. JS文件只会evaluate一次;

虽然JS文件仅evaluate一次,但是利用编译时webpack的能力,它将每个JS文件封装为模块化代码(`function (module, exports, __webpack_require__) { ...code... }`);

当每个页面重新加载该JS时,其实是填充到了各自页面级别的module缓存`installedModules`中;

这样,当每次页面引入该JS时,得到的就是,该页面下的该模块的JS对象;

优点:JS的evaluate只会一次,但页面对应的该JS模块会重新填充一次;

缺点:JS中模块化代码之外的代码只会执行一次,但是模块内的代码会执行N次(N个页面加载);

5.2 方式2. 每次页面加载JS文件时,都重新evaluate对应的JS代码,得到不同的JS对象;

优点:做到了运行时的天然隔离每个JS模块,即使以后更换为没有模块化的打包工具时,仍然能够无缝隔离;

缺点:每次新页面加载,都重新evaluate,引起同样的JS代码执行多次;

5.3 总结

综上所述,我们认为:页面中的JS模块独立性是一项运行时的能力;

这种能力不应该依赖于编译时的保证,以后即使不使用webpack编译工具时,仍然能够保证隔离,而且还提高了页面的安全性;

所以,选择方式2,页面每次加载都重新evaluate对应的JS代码;

6. 任务4. 单独JS文件的模块化管理

既然要支持单独的JS文件加载,那么就需要对所有的单独JS文件做模块化管理;

需要考虑几个方面:实现位置,递归依赖,路径转换;

实现位置指的是:模块化的功能在运行时实现,还是编译时实现;

递归依赖的问题,可以参考NodeJS中的模块化实现,是在首次加载JS文件前,先定义模块为空对象,然后执行时包裹一段模块化代码:`function (exports, require, module, __filename, __dirname) { ...code... }`;当发生递归式依赖时,传递之前已经定义的模块对象;

由此来看,模块化无论发生在编译时还是运行时,都需要解决递归依赖;

6.1 实现位置:编译时

在webpack构建中,如果是仅生成一个JS文件,是通过`installedModules`内部缓存了各个模块;如果要生成多个JS文件时,是通过`webpackJsonp`完成的,因此只需要对这块针对快应用添加适配,提供同步读取rpk中JS文件的能力,即可完成编译时模块化的能力;

编译时同时也需要完成源代码中相对路径到绝对路径的转换,这块都可以使用webpack现有的实现方式;

6.2 实现位置:运行时

运行时模块化增加这块机制的好处在于:可以摆脱对webpack编译工具的模块管理依赖,以后如果有其它的编译工具,那么也能够无缝支持;

6.3 总结

考虑到规范发版,开发的时间成本,本次先利用成熟的webpack工具,即:编译时能力解决,待以后再增加对应的运行时实现;

关于模块化中的`动态加载`模块的能力(`const variable1 = 'test'; require(variable1);`),以及异步加载的能力,由于需求不太紧迫,因此本次规范暂不考虑;

7. 任务5:流式加载

当前快应用项目构建的RPK文件是一个ZIP文件,里面根据运行时加载JS文件的顺序,相应的安排了每个文件在ZIP索引中的顺序;比如根据顺序,先后主要为:`manifest.json`,`app.js`,每个`page.js`;

这项任务指的是:如何安排独立JS文件的位置(哪些在页面JS之前与之后),让页面首屏渲染需要的文件能够保证内存级别的同步读取耗时最少(避免磁盘读引起的时间损耗);

也就是说,这项任务的核心是:是否有办法确定哪些JS是属于页面首屏渲染必须的?

经过分析之后,目前没有直接的办法确定,通过让开发者加标记FLAG也并不是一件好办法;

但可以换个思路考虑问题:假设所有的独立JS放在页面JS之前,因为它并不会被立即执行为为JS对象,所以放在页面之前也仅仅只是增加了RPK下载与ZIP解压缩这些独立JS的时间;目前看,这些时间相对较小;

所以实现思路上,可以将:单独JS全部放在app.js与页面JS之前,因为app.js也可能会用到独立JS;

8. 任务6:分包支持

当前快应用支持分包能力,非独立包又分为:页面模块包与基础包;

为了管理上的方便,建议:将所有的独立JS放置在一个统一的固定目录下,如:(`%PROJECT%/build/chunks/foo.js`);

定义一个`chunks`的文件夹,将独立JS在里面,当然这个目录下可能还存在子目录的路径;

8.1 非独立包

如果是非独立包的话,可以将所有的独立JS也就是对应的目录,移动到:`基础包`中;

8.2 独立包

如果是独立包的话,则将自己用到的独立JS放在该模块下;

9. 文章总结

经过上面几个任务的分析,为了使得v1070版本提供的规范尽快发版,将使用编译时的模块化能力,运行时仅提供文件的同步读取API,以支持:加载单独JS文件的规范;

针对于运行时模块化(即:动态加载)、异步加载的其它需求,将在后面收到开发者需求后再制定快应用联盟规范。

js后退页面不重新加载_快应用:支持加载单独JS文件的规范思考相关推荐

  1. 中yeti不能加载_将 PQ 查询加载到 Excel 中进行分析的三种常用的方式

    点击上方蓝字 关注星标★不迷路 岁月本长,忙者自促 虽然大部分时候经过PQ清洗的数据都是加载到Excel工作表中,但是PQ中还有另外两种将数据返回Excel中进行分析的方法. 三种不同的数据加载方式: ...

  2. 5 加盐_洗猪肚,加盐洗就错了!加这2样,5分钟洗净,猪肚不腥不臊更入味

    它是猪身上最好吃的部位.虽然有些贵,但是很多人却爱不完,它就是猪肚.猪肚是猪的胃,猪肚中含有大量的钙.钾.钠.镁.铁等元素和维生素A.维生素E.蛋白质.脂肪等成分,具有补虚损,健脾胃的作用.猪肚的经典 ...

  3. ajax java 图片加载_如何用Ajax加载服务器的图片

    用Ajax请求服务器的图片,并显示在浏览器中 前言 一直在数据库里面存的都是图片在服务器的地址,然后再到浏览器中显示,但是发现两个问题 第一:为了安全起见,js是无法读取本地的图片的,不然你写一个js ...

  4. mysql驱动为什么自动加载_为什么JDBC中加载驱动要使用反射?

    原文链接:https://www.cnblogs.com/homejim/p/8076481.html 在JDBC详解系列(一)之流程中,我将数据库的连接分解成了六个步骤. JDBC流程: 第一步:加 ...

  5. go语言 不支持动态加载_动态语言支持

    go语言 不支持动态加载 本文是我们名为" 高级Java "的学院课程的一部分. 本课程旨在帮助您最有效地使用Java. 它讨论了高级主题,包括对象创建,并发,序列化,反射等. 它 ...

  6. java ueditor 图片上传加水印_百度ueditor上传图片加水印的例子

    百度ueditor上传图片默认没有水印功能的如果我们要添加水印需要在程序上进行一些添加了,下面来看看百度ueditor上传图片加水印的例子吧. 打开UEditor压缩包下php目录中的上传类文件:Up ...

  7. epson彩色打印机加墨水_爱普生打印机墨盒如何加墨?

    展开全部 掌握以下几点步骤,即可轻松加墨. 1.首先,从打印机上取下墨盒,32313133353236313431303231363533e4b893e5b19e31333365646234这里就不好 ...

  8. java游戏有个按技能是旋风_快打旋风加难技能增强版

    修改者:日月光华 快打旋风的改版中,有个"技能修改与敌人替换版".我很喜欢敌人替换的效果,不过网上的"技能修改与敌人替换版"毕竟略嫌粗糙,于是本人重新制定敌人替 ...

  9. 七牛 java 加水印_七牛云图片加水印

    目标:用户登录进平台后,他看到的所有图片都要以他的用户名加上水印. 1.首先说下七牛加水印的方法,首先附上官网地址: https://developer.qiniu.com/dora/manual/1 ...

最新文章

  1. jQuery与CSS3的选择器
  2. Debian 系统初体验
  3. mysql的基准测试_mysql基准测试 -benchmarks
  4. python无法打开 firefox浏览器_【求助】pycharm不能打开火狐浏览器
  5. 藉上帝之旨,行时代之命的文学长征
  6. acwing 327. 玉米田
  7. 【计算机视觉】基于OpenCV的人脸识别
  8. 一个使用指针的简单程序
  9. 工欲善其事必先利其器(一)
  10. 计算机2013知识,【2013年计算机基础知识习题与答案(三)】- 环球网校
  11. sqlserver递归
  12. 前缀树(字典树,单词查找树,Trie树)
  13. Eclipse IDE 2022的下载与安装
  14. 移动通信原理学习笔记之一
  15. android关机铃声代码,android系统添加关机铃声
  16. CSA云安全指南V4.0 D9 D10
  17. TI | TM4C123Gx单片机之---ADC笔记
  18. 如何在markdown中打出上标、下标和一些特殊符号 from jianshuer 这是朕的江山
  19. Vue进阶(四十四):vue 图片加载完成事件
  20. [aria2c]使用aria2c下载“任务出错”的bt种子

热门文章

  1. Python爬虫自学之第(⑤)篇——爬取某宝商品信息
  2. 如何关闭线程池?会创建不会关闭?调用关闭方法时线程池里的线程如何反应?
  3. Error:Connection timed out: connect
  4. android 获取webView高度,设置webView高度
  5. 1 微信公众号开发 服务器配置 有什么用
  6. oracle 10 expdp impdp 导入、导出
  7. 关于win32与win64的兼容性问题
  8. android icu4c 7.1编译报错,android4.0编译系统时候遇到的错误集
  9. linux文件系统的设计,基于Linux的文件系统设计.doc
  10. springboot 整合druid