首页vns威尼斯城官网登入 › 的模块提案为在服务器端的 vns威尼斯城官网登入JavaScript,它允许你将一个页面涉及到的所有零星图片都包含到一张大图中去

的模块提案为在服务器端的 vns威尼斯城官网登入JavaScript,它允许你将一个页面涉及到的所有零星图片都包含到一张大图中去

按需加载

同时,我们考虑通过尽量减小页面体积来提升页面打开速度,在业务上我们将页面划分为一个个楼层组件,以京东美妆馆为例,页面中从上而下分为首焦、至IN尖货、今日特惠、潮流前沿、口碑榜单这么几个楼层组件,其实这个页面还有很长,内容非常多且复杂。

vns威尼斯城官网登入 1

之前我们的做法是整个页面直出,这样一次性加载的内容会非常多,为了提升打开速度,我们考虑通过按需加载的方式来优化页面的加载。我们在页面中只放每一个楼层的框架性代码,楼层的模板和数据都通过异步的方式去拉取,来实现楼层组件的按需加载,同时我们可以对模板以及数据进行缓存,以此来减少请求,做更极致的优化。在开发中我们以正常组件的方式去开发整个页面,随后通过编译工具,在代码编译阶段自动将楼层的模板抽离成一个独立的JS文件,并给楼层容器打上标记位,通过页面加载逻辑去按需拉取模板,再进行渲染。

通过给楼层容器和模板分别加上标记位 o2-out-tpl-wrapper o2-out-tpl

vns威尼斯城官网登入 2

在编译时自动将指定的模板代码抽离成独立js文件

vns威尼斯城官网登入 3

并且给楼层容器打上标记

vns威尼斯城官网登入 4

同时在逻辑脚本适当位置自动加入模板的版本

vns威尼斯城官网登入 5

通过上述步骤,实现按需加载的自动化生成,在提升性能的同时,很好地解放我们生产力。

模板模块

我们可以将任何一段可复用的模板代码放到一个 smarty
文件中,这样就可以定义一个模板模块。在 widget 目录下的 smarty
模板(本文仅以 Smarty 模板为例)即为模板模块,例如 common 子系统的
widget/nav/ 目录

├── nav.css
├── nav.js
└── nav.tpl

下 nav.tpl 内容如下:

<nav id="nav" class="navigation" role="navigation">
    <ul>
        <%foreach $data as $doc%>
        <li class="active">
            <a href="#section-{$doc@index}">
                <i class="icon-{$doc.icon} icon-white"></i>{$doc.title}
            </a>
        </li>
        <%/foreach%>
    </ul>
</nav>

然后,我们只需要一行代码就可以调用这个包含 smarty、JS、CSS
资源的模板模块,

// 调用模块的路径为 子系统名称:模板在 widget 目录下的路劲
{widget name="common:widget/nav/nav.tpl" }

这个模板模块(nav)目录下有与模板同名的 JS、CSS
文件,在模板被执行渲染时这些资源会被自动加载。如上所示,定义 template
模块的时候,只需要将 template 所依赖的 JS 模块、CSS
模块存放在同一目录(默认 JavaScript 模块、CSS 模块与 Template
模块同名)下即可,调用者调用 Template
模块只需要写一行代码即可,不需要关注所调用的 template
模块所依赖的静态资源,模板模块会帮助我们自动处理依赖关系以及资源加载。

非模块化资源

在实际开发过程中可能存在一些不适合做模块化的静态资源,那么我们依然可以通过声明依赖关系来托管给静态资源管理系统来统一管理和加载,

{require name="home:static/index/index.css" }

如果通过如上语法可以在页面声明对一个非模块化资源的依赖,在页面运行时可以自动加载相关资源。

采用query更新缓存的方式实际上要覆盖线上文件的,index.html和a.js总有一个先后的顺序,从而中间出现一段或大或小的时间间隔。尤其是当页面是后端渲染的模板的时候,静态资源和模板是部署在不同的机器集群上的,上线的过程中,静态资源和页面文件的部署时间间隔可能会非常长,对于一个大型互联网应用来说即使在一个很小的时间间隔内,都有可能出现新用户访问。在这个时间间隔中,访问了网站的用户会发生什么情况呢?

Model View Controller

FreeMarker(FreeMarker Template Language) 一个java类库 view层完全独立
显示逻辑和业务逻辑分离 轻量级框架 不需要Servlet环境
HTML静态化
Template + DataModel = HTML
Java代码决定读取哪一个Template
FreeMarker模板不编译成类,不能写任何java代码,严格的MVC分离
性能优于JSP 支持JSP标签

宏定义是什么?

前端框架主要为了解决什么问题?如何解决?
1.资源定位
工程路径 --> 部署路径,
相对路径 --> 绝对路径 + md5戳 + 域名 -->
解决版本迭代后静态资源缓存在客户端的问题,
实现模块独立,任务文件间都可以进行内嵌
2.模块化开发
核心问题:依赖管理和加载
构建工具只负责生成依赖关系表 框架自己绝对什么时候加载哪些资源

  • 规范

    • 开发规范
      • 模块化开发:js模块化,css模块化
      • 组件化开发:模板,js,css维护在一起
    • 部署规范
      • 采用nodejs后端,基本部署规范应该参考
        express
        项目部署
      • 按版本号做非覆盖式发布
      • 公共模块可发布给第三方共享
  • 框架

    • js模块化框架,支持请求合并,按需加载等性能优化点
  • 工具

    • 可以编译stylus为css
    • 支持js、css、图片压缩
    • 允许图片压缩后以base64编码形式嵌入到css、js或html中
    • 与ci平台集成
    • 文件监听、浏览器自动刷新
    • 本地预览、数据模拟

模块化框架

  • 模块管理
  • 资源加载
  • 性能优化(按需,请求合并)
  • 组件开发的基础框架

编译预览

项目实例

下面我们来看一下在一个实际项目中,如果在通过页面来调用各种类型的
widget,首先是目录结构:

├── common
│   ├── fis-conf.js
│   ├── page
│   ├── plugin
│   ├── static
│   └── widget
└── photo
    ├── fis-conf.js
    ├── output
    ├── page
    ├── static
    ├── test
    └── widget

我们有两个子系统,一个 common 子系统(用作通用),一个业务子系统,page
目录用来存放页面,widget 目录用来存放各种类型的模块,static
用于存放非模块化的静态资源,首先我们来看一下 photo/page/index.tpl
页面的源码,

{extends file="common/page/layout/layout.tpl"}
{block name="main"}
    {require name="photo:static/index/index.css"}
    {require name="photo:static/index/index.js"}
    <h3>demo 1</h3>
    <button id="btn">Button</button>
    {script type="text/javascript"}
        // 同步调用 jquery
        var $ = require('common:widget/jquery/jquery.js');

        $('#btn').click(function() {
            // 异步调用 respClick 模块
            require.async(['/widget/ui/respClick/respClick.js'], function() {
                respClick.hello();
            });
        });
    {/script}

    // 调用 renderBox 模块
    {widget name="photo:widget/renderBox/renderBox.tpl"}
{/block}

第一处代码是对非模块化资源的调用方式;第二处是用 require 的方式调用一个
JavaScript 模块;第三处是通过 require.async 通过异步的方式来调用一个
JavaScript 模块;最后一处是通过 widget 语法来调用一个模板模块。 respclick
模块的源码如下:

exports.hello = function() {
    alert('hello world');
};

renderBox 模板模块的目录结构如下:

└── widget
    └── renderBox
        ├── renderBox.css
        ├── renderBox.js
        ├── renderBox.tpl
        └── shell.jpeg

虽然 renderBox 下面包括 renderBox.js、renderBox.js、renderBox.tpl
等多种模块,我们再调用的时候只需要一行代码就可以了,并不需要关注内部的依赖,以及各种模块的初始化问题。

项目实例

下面我们来看一下在一个实际项目中,如果在通过页面来调用各种类型的
widget,首先是目录结构:

├── common
│   ├── fis-conf.js
│   ├── page
│   ├── plugin
│   ├── static
│   └── widget
└── photo
    ├── fis-conf.js
    ├── output
    ├── page
    ├── static
    ├── test
    └── widget

我们有两个子系统,一个 common 子系统(用作通用),一个业务子系统,page
目录用来存放页面,widget 目录用来存放各种类型的模块,static
用于存放非模块化的静态资源,首先我们来看一下 photo/page/index.tpl
页面的源码,

{extends file="common/page/layout/layout.tpl"}
{block name="main"}
    {require name="photo:static/index/index.css"}
    {require name="photo:static/index/index.js"}
    <h3>demo 1</h3>
    <button id="btn">Button</button>
    {script type="text/javascript"}
        // 同步调用 jquery
        var $ = require('common:widget/jquery/jquery.js');

        $('#btn').click(function() {
            // 异步调用 respClick 模块
            require.async(['/widget/ui/respClick/respClick.js'], function() {
                respClick.hello();
            });
        });
    {/script}

    // 调用 renderBox 模块
    {widget name="photo:widget/renderBox/renderBox.tpl"}
{/block}

第一处代码是对非模块化资源的调用方式;第二处是用 require 的方式调用一个
JavaScript 模块;第三处是通过 require.async 通过异步的方式来调用一个
JavaScript 模块;最后一处是通过 widget 语法来调用一个模板模块。 respclick
模块的源码如下:

exports.hello = function() {
    alert('hello world');
};

renderBox 模板模块的目录结构如下:

└── widget
    └── renderBox
        ├── renderBox.css
        ├── renderBox.js
        ├── renderBox.tpl
        └── shell.jpeg

虽然 renderBox 下面包括 renderBox.js、renderBox.js、renderBox.tpl
等多种模块,我们再调用的时候只需要一行代码就可以了,并不需要关注内部的依赖,以及各种模块的初始化问题。

 

fis开源项目前端工程模块化:

  • 上线的a.js不是同名文件覆盖,而是文件名+hash的冗余,所以可以先上线静态资源,再-
    上线html页面,不存在间隙问题;
  • 遇到问题回滚版本的时候,无需回滚a.js,只须回滚页面即可;
    由于静态资源版本号是文件内容的hash,因此所有静态资源可以开启永久强缓存,只有更新了内容的文件才会缓存失效,缓存利用率大增

技术选型

Athena本地工具早期技术选型是 Yeoman + Gulp
的方式,但后来由于安装、更新非常麻烦,命令太长很难打的原因,我们改成了自己开发一个全局安装包的方式,编译核心使用的还是
Gulpvinyl-fs 来实现文件流处理,通过 ES6 Promise
来进行编译流程控制,最小以页面为单位,经过一系列编译任务,最后产出编译好的文件。

vns威尼斯城官网登入 6

JavaScript 模块

上面我们介绍了一个模板模块是如何定义、调用以及处理依赖的,接下来我们来介绍一下模板模块所依赖的
JavaScript 模块是如何来处理模块交互的。我们可以将任何一段可复用的
JavaScript 代码放到一个 JS 文件中,这样就可以定义为一个 JavaScript
类型的模块,我们无须关心“ define ”闭包的问题,我们可以获得“ CommonJS
”一样的开发体验,下面是 nav.js 中的源码.

// common/widget/nav/nav.js
var $ = require('common:widget/jquery/jquery.js');

exports.init = function() {
    ...
};

我们可以通过 require、require.async 的方式在任何一个地方(包括
html、JavaScript 模块内部)来调用我们需要的 JavaScript 类型模块,require
提供的是一种类似于后端语言的同步调用方式,调用的时候默认所需要的模块都已经加载完成,解决方案会负责完成静态资源的加载。require.async
提供的是一种异步加载方式,主要用来满足“按需加载”的场景,在 require.async
被执行的时候才去加载所需要的模块,当模块加载回来会执行相应的回调函数,语法如下:

// 模块名: 文件所在 widget 中路径
require.async(["common:widget/menu/menu.js"], function( menu ) {
    menu.init();
});

一般 require 用于处理页面首屏所需要的模块,require.async
用于处理首屏外的按需模块。

CSS 模块

在模板模块中以及 JS 模块中对应同名的 CSS 模块会自动与模板模块、JS
模块添加依赖关系,进行加载管理,用户不需要显示进行调用加载。那么如何在一个
CSS 模块中声明对另一个 CSS
模块的依赖关系呢,我们可以通过在注释中的@require
字段标记的依赖关系,这些分析处理对 html 的 style 标签内容同样有效,

/**
 * demo.css
 * @require reset.css
 */

这就是为什么大型web应用在版本上线的过程中经常会较集中的出现前端报错日志的原因,也是一些互联网公司选择加班到半夜等待访问低峰期再上线的原因之一。

创建项目结构

在执行创建命令时,Athena会从管理平台下载自定义好的项目模板,可以根据模板创建项目、模块、页面、和组件。Athena有四个创建命令:

通过执行 $ ath app demo 命令就可以生成定义好目录结构的项目。

vns威尼斯城官网登入 7

随后可以通过 $ ath module home来创建一个业务模块;

通过 $ ath page index 来创建页面;

通过 $ ath widget widgetName 来创建组件。

总体架构

为了实现一种自然、便捷、高性能、一体化的模块化方案,我们需要解决以下一些问题,

  • 模块静态资源管理,一般模块总会包含 JavaScript、CSS
    等其他静态资源,需要记录与管理这些静态资源
  • 模块依赖关系处理,模块间存在各种依赖关系,在加载模块的时候需要处理好这些依赖关系
  • 模块加载,在模块初始化之前需要将模块的静态资源以及所依赖的模块加载并准备好
  • 模块沙箱(模块闭包),在 JavaScript
    模块中我们需要自动对模块添加闭包用于解决作用域问题

** 使用编译工具来管理模块 **

我们可以通过编译工具(自动化工具)
对模块进行编译处理,包括对静态资源进行预处理(对 JavaScript
模块添加闭包、对 CSS 进行 LESS
预处理等)、记录每个静态资源的部署路径以及依赖关系并生成资源表(resource
map)。我们可以通过编译工具来托管所有的静态资源,这样可以帮我们解决模块静态资源管理、模块依赖关系、模块沙箱问题。

** 使用静态资源加载框架来加载模块 **

那么如何解决模块加载问题,我们可以通过静态资源加载框架来解决,主要包含前端模块加载框架,用于
JavaScript 模块化支持,控制资源的异步加载。后端模块化框架,用于解决
JavaScript 同步加载、CSS
和模板等模块资源的加载,静态资源加载框架可以用于对页面进行持续的自适应的前端性能优化,自动对页面的不同情况投递不同的资源加载方案,帮助开发者管理静态资源,抹平本地开发到部署上线的性能沟壑。
编译工具和静态资源加载框架的流程图如下:

vns威尼斯城官网登入 8

模块化为打包部署带来的极大不便

传统的模块化方案更多的考虑是如何将代码进行拆分,但是当我们部署上线的时候需要将静态资源进行合并(打包),这个时候会发现困难重重,每个文件里
只能有一个模块,因为模块使用的是“匿名定义”,经过一番研究,我们会发现一些解决方案,无论是“
combo 插件”还是“ flush
插件”,都需要我们修改模块化调用的代码,这无疑是雪上加霜,开发者不仅仅需要在本地开发关注模块化的拆分,在调用的时候还需要关注在一个请求里面加载哪
些模块比较合适,模块化的初衷是为了提高开发效率、降低维护成本,但我们发现这样的模块化方案实际上并没有降低维护成本,某种程度上来说使得整个项目更加
复杂了。

首先我们来看一下一个 web
项目是如何通过“一体化”的模块化方案来划分目录结构:

vns威尼斯城官网登入 9

  • 站点(site):一般指能独立提供服务,具有单独二级域名的产品线。如旅游产品线或者特大站点的子站点(lv.baidu.com)。
  • 子系统(module):具有较清晰业务逻辑关系的功能业务集合,一般也叫系统子模块,多个子系统构成一个站点。子系统(module)包括
    两类: common 子系统,
    为其他业务子系统提供规范、资源复用的通用模块;业务子系统:,根据业务、URI
    等将站点进行划分的子系统站点。
  • 页面(page): 具有独立 URL 的输出内容,多个页面一般可组成子系统。
  • 模块(widget):能独立提供功能且能够复用的模块化代码,根据复用的方式不同分为
    Template 模块、JS 模块、CSS 模块三种类型。
  • 静态资源(static):非模块化资源目录,包括模板页面引用的静态资源和其他静态资源(favicon,crossdomain.xml
    等)。

前端模块(widget),是能独立提供功能且能够复用的模块化代码,根据复用的方式不同分为
Template 模块、JS 模块、CSS 模块三种类型,CSS 组件,一般来说,CSS
模块是最简单的模块,它只涉及 CSS 代码与 HTML 代码; JS
模块,稍为复杂,涉及 JS 代码,CSS 代码和 HTML 代码。一般,JS
组件可以封装 CSS 组件的代码; Template 模块,涉及代码最多,可以综合处理
HTML、JavaScript、CSS 等各种模块化资源,一般情况,Template 会将 JS
资源封装成私有 JS 模块、CSS 资源封装成自己的私有 CSS
模块。下面我们来一一介绍这几种模块的模块化方案。

性能优化既是一个工程问题,又是一个统计问题。优化性能时如果只关注一个页面的首次加载是很片面的。还应该考虑全站页面间跳转、项目迭代后更新资源等情况下的优化策略。##\

此时,我们又引入了一个新的问题:如何决定哪些文件被打包?
从经验来看,项目初期可以采用人工配置的方式来指定打包情况,比如:

但随着系统规模的增大,人工配置会带来非常高的维护成本,此时需要一个辅助系统,通过分析线上访问日志和静态资源组合加载情况来自动生成这份配置文件,系统设计如图:

vns威尼斯城官网登入 10

至此,我们通过基于表的静态资源管理系统和三个模板接口实现了几个重要的性能优化原则,现在我们再来回顾一下前面的性能优化原则分类表,剔除掉已经做到了的,看看还剩下哪些没做到的:

  • 请求数量: 拆分初始化负载
  • 缓存利用:使用Ajax可缓存
  • 页面结构:尽早刷新文档的输出

拆分初始化负载
的目标是将页面一开始加载时不需要执行的资源从所有资源中分离出来,等到需要的时候再加载。工程师通常没有耐心去区分资源的分类情况,但我们可以利用组件化框架接口来帮助工程师管理资源的使用。还是从例子开始思考,如果我们有一个js文件是用户交互后才需要加载的,会怎样呢:

<html><head> 
<title>page</title> 
<?php require_static('jquery.js'); ?> 
<?php require_static('bootstrap.css'); ?>
 <?php require_static('bootstrap.js'); ?>
 <!--[ CSS LINKS PLACEHOLDER ]-->
</head>
<body>
 <?php load_widget('a'); ?> 
<?php load_widget('b'); ?> 
<?php load_widget('c'); ?> 
<?php script('start'); ?>
 <script> $(document.body).click(function(){
 require.async('dialog.js', function(dialog){
 dialog.show('you catch me!');
 }); 
}); 
</script>
 <?php script('end'); ?>
 <!--[ SCRIPTS PLACEHOLDER ]-->
</body>
</html>

很明显,dialog.js
这个文件我们不需要在初始化的时候就加载,因此它应该在后续的交互中再加载,但文件都加了md5戳,我们如何能在浏览器环境中知道加载的url呢?

答案就是:把静态资源表的一部分输出在页面上,供前端模块化框架加载静态资源。

我就不多解释代码的执行过程了,大家看到完整的html输出就能理解是怎么回事了:

<html><head> <title>page</title>
 <link rel="stylesheet" type="text/css" href="/pkg/lib_afec33f.css"/>
 <link rel="stylesheet" type="text/css" href="/pkg/widgets_af23ce5.css"/><
/head><body>
 <div> content of module a </div> 
<div> content of module b </div> 
<div> content of module c </div> 
<script type="text/javascript" src="/pkg/lib_cef213d.js"></script>
 <script type="text/javascript" src="/pkg/widgets_22feac1.js"></script> 
<script> //将静态资源表输出在前端页面中 
require.config({ res : { 'dialog.js' : '/dialog_fa3df03.js' } }); 
</script> 
<script> $(document.body).click(function(){ //require.async接口查表确定加载资源的url require.async('dialog.js', function(dialog){ dialog.show('you catch me!');
 }); }); 
</script>
</body>
</html>

dialog.js不会在页面以script
src的形式输出,而是变成了资源注册,这样,当页面点击触发require.async执行的时候,async函数才会查表找到资源的url并加载它,加载完毕后触发回调函数。以上框架示例我实现了一个java-jsp版的,有兴趣的同学请看这里:https://github.com/fouber/fis-java-jsp

到目前为止,我们又以架构的形式实现了一项优化原则(拆分初始化负载),回顾我们的优化分类表,现在仅有两项没能做到了:

  • 缓存利用:使用Ajax可缓存
  • 页面结构:尽早刷新文档的输出

剩下的两项优化原则要做到并不容易,真正可缓存的Ajax在现实开发中比较少见,而
尽早刷新文档的输出原则facebook在2010年的velocity上
提到过,就是BigPipe技术。当时facebook团队还讲到了Quickling和PageCache两项技术,其中的PageCache算是比较彻底的实现Ajax可缓存的优化原则了。由于篇幅关系,就不在此展开了,后续还会撰文详细解读这两项技术。
总结
其实在前端开发工程管理领域还有很多细节值得探索和挖掘,提升前端团队生产力水平并不是一句空话,它需要我们能对前端开发及代码运行有更深刻的认识,对性能优化原则有更细致的分析与研究。在前端工业化开发的所有环节均有可节省的人力成本,这些成本非常可观,相信现在很多大型互联网公司也都有了这样的共识。
问题
1.每个文件改动后生产md5后缀,多次上线后线上会产生:

···
a_xxx1.js
a_xxx2.js
a_xxx3.js
···

也许是我要洁癖,但是这样循环N次后,上线的全量包会越来约大,如何处理这个的?

  • 每次上线,只有修改过的文件才会出现新的md5戳,所以文件冗余没有想象中的那么多
    比较频繁修改的业务模块大概每年会产生100m左右的冗余,预计每3年有必要清理一次
  • 清理的时候,写一个脚本,根据文件名规则找到最后访问的文件然后删除其他的。活着干脆某次上线把发布后的文件之外的其他文件都清理一次,总之这个不成问题

**
2.HTML是后端们JAVA写的动态页面,前端们只写JS,css,然后静态资源发布后,生成了新的md5,那么JAVA写的页面里怎么去获取这个新的MD5,以保证加载正确的静态资源。是要在前端静态文件服务器上搞个监控,把新的MD5存某个地方,然后JAVA那边每次请求页面都要获取下新的MD5,替换生成新的链接?**

java写动态页面不是?不要让他们在java的模板中写这样的代码:

<script src="a.js"></script>

改成写这样的代码:

<fis:require id="a.js"/>

这个 fis:require
的标签,是扩展了jsp的自定义标签。然后,构建工具扫描前端写的js、css,建立一个map资源表,内容大概是:

{ "a.js" : { 
"url": "/static/js/a_0fa0c3b.js", 
"deps": [ "b.js" ] },
 "b.js" : { 
"url": "/static/js/b_4cb04f9.js" 
}
}

然后,我们把这个资源表和java的动态页面放在一起。前面提到的模板中的那个
fis:require 标签,在模板解释执行的时候,会去查这个map表,根据 a.js
这个资源id找到它的带md5戳的url就是“/static/js/a_0fa0c3b.js”,同时还知道这个文件依赖了
b.js
就顺便把b.js的url也收集起来。
最后,在java动态页面生成html之前,把收集到的两个js标签用字符串替换的方式生成script标签插入到页面上,得到:

<script src="/static/js/a_0fa0c3b.js"></script>
<script src="/static/js/b_4cb04f9.js"></script>

有一个项目展示了这个思路的整个实现过程:
https://github.com/fouber/fis-java-jsp
**
这个
资源表(map)和fis:require标签是解决这个问题的重点,map是构建工具生成的,通过静态扫描整个前端工程代码得到。map的作用是记录资源的依赖关系和部署路径,然后交给资源管理框架去决定资源加载策略,因此我们最终要把map跟java动态语言部署在一起。fis:require是运行在后端动态模板语言中的资源管理框架,它依赖map表的数据信息,你可以把它理解成一个写在模板引擎中的requirejs。设计这个框架的目的是彻底替<script>标签和<link>标签这种字面量资源定位符,把它们改造成可编程的资源管理框架,在模板渲染的过程中收集页面所用资源,实现去重、依赖管理、资源加载、带md5等等功能**

3、构建工具扫描前端写的js、css,是根据ID匹配文件名截取文件名上的MD5还是扫描文件内容生成MD5?然后生成MAP。
扫描所有文件,计算文件的摘要,然后生成url。再以文件工程路径为key,建立map表,整个过程不会替换任何文件内容,只是建立表。

4、JS源文件是PUSH到server1,然后在server1上fis编译JS,后端代码是放server2,构建工具是往server1上扫描编译好后的js吧,还是源文件?
都是线下编译。线下设置好js、css要发布的server1的域名、路径,然后release,生成编译后的代码和map,把代码发布到server1上,把map发布到server2上,map中写入的js、css的路径都是符合预期的。构建工具扫描的并不是简单的编译后的结果。我们用工具读取所有文件,然后逐个编译,然后把编译后的结果发布为带md5戳的资源,同时在map中记录的是
源码的文件路径(也就是开发中的工程路径)
和 发布后的资源路径
的映射关系,工程路径 ≠
部署路径,它们有很大差别。部署路径带md5戳,而且可能变换了发布目录。这样我们采用源码的工程路径作为文件id,在java等动态语言中也可以使用工程路径去加载资源,看起来非常符合人类的直觉。

5、我们后端是groovy语言和grails框架写的页面,fis支持吗?
其他语言可以根据fis的map.json结构,和fis资源管理的思想自己实现这个框架,并不复杂

6.map.json的升级问题,有两个方案:

  • 非覆盖式发布map.json,配置fis,让map.json发布的时候带一个构建时间戳,然后把这个时间戳写入到java模板中,先发布map.json,但是线上运行的java页面读取的还是旧的map,然后部署模板,模板中声明了使用新版本的map.json,问题解决
  • 持久化模板中的map数据。模板引擎一般只有再模板修改后才会重新编译模板,你把读取map的逻辑变成编译后静态写入的结果,下次上线后,先覆盖map.json,这个时候所有模板都还只是使用上一个版本的map数据,然后发布模板,再触发一下模板编译,读入新的map
    7.放在require.asyn里面 一样是异步加载 但是在编译的时候
    直接把md5后的名字替换了dialog.js这名字 浏览器运行时
    在需要的时候加载的也还是对应的资源

require.async要做两件事,一个是加载资源,一个是加载完成后回调。

加载资源不仅仅是加载资源本身,还要加载依赖的资源,以及依赖的依赖。比如这个dialog.js,并不是独立资源,它可能还会依赖其他文件,假设它依赖了component.js和dialog.css两个资源,component.js又依赖component.css,那么我们得到一颗依赖树:

dialog.js
       ├ dialog.css
       └ component.js
       └ component.css

问题来了,我们怎么告诉require.async,在加载dialog.js的时候,要一并加载其他3个资源呢?我们势必要将依赖关系表放在前端才能实现这个优化,也就有了针对require.async加载的依赖配置项。有这个依赖表,还意味着我们根本没必要把
require.async(id, callback)
接口设计成 require.async(url, callback)
,因为保留id,在查询依赖关系的时候最方便。
当然,你或许会想到“我们用文件的url建立依赖关系不就行了么?”,这里还涉及到另外一个问题,就是我们加载dialog.js,未必就是加载dialog.js这个文件的独立url,如果它被打包了,我们其实要加载的是它所在资源包的url,比如dialog.js和component.js合并成了aio.js,我们虽然require.async('dialog.js'),但实际上请求的是aio.js这个url。
你或许又想到了“我们用构建工具把require.async的资源路径改成打包后的url地址不就行了?”,恩,这里又涉及到另外一个资源加载问题:动态请求。比如我们需要根据一些运行时的参数来加载模块:

var mod = isIE ? 'fuck.js' : 'nice.js';
require.async(mod, function(m){
 //blablabla
});

前端只有资源表的好处是支持动态加载模块,只要把依赖表输出给前端,就能实现真正的按需加载,这是单纯的静态分析所无法实现的。
此外,require.async还要监听资源加载完毕时间,require.async(id,
callback)
这样的设计,可以让define(id,
factory)接口被调用的时候,根据id派发模块加载完毕事件,如果把require.async设计成使用url作为参数,那就要改成通过监听script的onload事件来判断资源加载完成与否,这样也麻烦一些。

8.实际开发debug调试的时候和最终打包发布线上这之间是如何区分的
这其实是一个构建工具的使用技巧,本地开发和上线部署的构建过程稍微有一些差别而已,上线部署的构建过程需要给资源加上domain。

以fis为例,我们把压缩、资源合并、加md5,加域名等构建操作变成命令行的参数,比如我们本地开发这样的命令:

fis release --dest ../dev

就是构建一下代码,把结果发布到dev目录下,然后我们在dev目录下启动服务器进行本地开发调试,而当我们要提测的时候,并不是用dev目录的东西,而是真的源码又发布一次:

fis release --optimize --hash --pack --dest ../test

这回,我们对代码进行了压缩、加md5、资源合并操作,并发布到了另外一个test目录中,测试是在test目录下进行的。

最终上线,我们也不是使用的test目录下的代码,而是又从源码重新发布一份:

fis release --optimize --hash --pack --domain --dest ../prod

有多了一个 --domain
参数,给资源加上CDN的域名,最终上线用的是prod里的代码。设计原则是始终从源码构建出结果,构建结果可能是开发中的,可能是提测用的,也可能是部署到生产环境的。

作为构建构建,至少要保证针对不同环境的构建代码逻辑是等价的,不能引入额外的不确定因素导致测试和部署结果不一致

摘自fouber前端工程与性能优化

管理平台

性能优化一直是前端工程师探索的课题,很多时候就是资源的分配问题,也就是资源管理。为了更好地配合本地构建工具来管理资源,我们搭建了管理平台。我们来看下,结合本地构建工具和管理平台,工作流程变成了怎样?

模块化是一种处理复杂系统分解成为更好的可管理模块的方式,它可以把系统代码划分为一系列职责单一,高度解耦且可替换的模块,系统中某一部分的变化将如何影响其它部分就会变得显而易见,系统的可维护性更加简单易得。

前端模块化带来的性能问题

很多主流的模块化解决方案通过 JavaScript
运行时来支持“匿名闭包”、“依赖分析”和“模块加载”等功能,例如“依赖分析”需要在
JavaScript
运行时通过正则匹配到模块的依赖关系,然后顺着依赖链(也就是顺着模块声明的依赖层层进入,直到没有依赖为止)把所有需要加载的模块按顺序一一加载完毕,
当模块很多、依赖关系复杂的情况下会严重影响页面性能。

也就是a.js发布出来后被修改了文件名,产生一个新文件,并不是覆盖已有文件。其中”_82244e91”这串字符是根据a.js的文件内容进行hash运算得到的,只有文件内容发生变化了才会有更改。由于将文件发布为带有hash的新文件,而不是同名文件覆盖,因此不会出现上述说的那些问题。同时,这么做还有其他的好处:

我们是如何做好前端工程化和静态资源管理

2016/07/30 · 基础技术 ·
工程化,
静态资源

原文出处:
凹凸实验室   

vns威尼斯城官网登入 11

随着互联网的发展,我们的业务也日益变得更加复杂且多样化起来,前端工程师也不再只是做简单的页面开发这么简单,我们需要面对的十分复杂的系统性问题,例如,业务愈来愈复杂,我们要如何清晰地梳理;团队人员愈来愈多,我们要如何更好地进行团队协作;功能愈来愈多,我们要如何保证页面的性能不至于下降,等等。所有的这些都可以归结为如何提升开发体验和性能问题。

编译工具

自动化工具会扫描目录下的模块进行编译处理并输出产出文件:

静态资源,经过编译处理过的 JavaScript、CSS、Image 等文件,部署在 CDN
服务器自动添加闭包,我们希望工程师在开发 JavaScript
模块的时候不需要关心” define
”闭包的事情,所以采用工具自动帮工程师添加闭包支持,例如如上定义的 nav.js
模块在经过自动化工具处理后变成如下,

define('common:widget/nav/nav.js', function( require, exports, module ) {
    // common/widget/nav/nav.js
    var $ = require('common:widget/jquery/jquery.js');

    exports.init = function() {
        ...
    };
});

模板文件,经过编译处理过的 smarty 文件,自动部署在模板服务器

资源表,记录每个静态资源的部署路径以及依赖关系,用于静态资源加载框架
静态资源加载框架(SR Management System)会加载 source maps
拿到页面所需要的所有模块以及静态资源的 url,然后组织资源输出最终页面。

JavaScript 模块

上面我们介绍了一个模板模块是如何定义、调用以及处理依赖的,接下来我们来介绍一下模板模块所依赖的
JavaScript 模块是如何来处理模块交互的。我们可以将任何一段可复用的
JavaScript 代码放到一个 JS 文件中,这样就可以定义为一个 JavaScript
类型的模块,我们无须关心“ define ”闭包的问题,我们可以获得“ CommonJS
”一样的开发体验,下面是 nav.js 中的源码.

// common/widget/nav/nav.js
var $ = require('common:widget/jquery/jquery.js');

exports.init = function() {
    ...
};

我们可以通过 require、require.async 的方式在任何一个地方(包括
html、JavaScript 模块内部)来调用我们需要的 JavaScript 类型模块,require
提供的是一种类似于后端语言的同步调用方式,调用的时候默认所需要的模块都已经加载完成,解决方案会负责完成静态资源的加载。require.async
提供的是一种异步加载方式,主要用来满足“按需加载”的场景,在 require.async
被执行的时候才去加载所需要的模块,当模块加载回来会执行相应的回调函数,语法如下:

// 模块名: 文件所在 widget 中路径
require.async(["common:widget/menu/menu.js"], function( menu ) {
    menu.init();
});

一般 require 用于处理页面首屏所需要的模块,require.async
用于处理首屏外的按需模块。

对于静态资源缓存更新的问题,目前来说最优方案就是
基于文件内容的hash版本冗余机制

了。也就是说,我们希望项目源码是这么写的:

编译缓存

在图片压缩和sass编译时,开启文件缓存,将已经编译过且没有改动的文件过滤掉,不再编译,大幅提升编译速度。

CSS 模块

在模板模块中以及 JS 模块中对应同名的 CSS 模块会自动与模板模块、JS
模块添加依赖关系,进行加载管理,用户不需要显示进行调用加载。那么如何在一个
CSS 模块中声明对另一个 CSS
模块的依赖关系呢,我们可以通过在注释中的@require
字段标记的依赖关系,这些分析处理对 html 的 style 标签内容同样有效,

/**
 * demo.css
 * @require reset.css
 */

模板模块

我们可以将任何一段可复用的模板代码放到一个 smarty
文件中,这样就可以定义一个模板模块。在 widget 目录下的 smarty
模板(本文仅以 Smarty 模板为例)即为模板模块,例如 common 子系统的
widget/nav/ 目录

├── nav.css
├── nav.js
└── nav.tpl

下 nav.tpl 内容如下:

<nav id="nav" class="navigation" role="navigation">
    <ul>
        <%foreach $data as $doc%>
        <li class="active">
            <a href="#section-{$doc@index}">
                <i class="icon-{$doc.icon} icon-white"></i>{$doc.title}
            </a>
        </li>
        <%/foreach%>
    </ul>
</nav>

然后,我们只需要一行代码就可以调用这个包含 smarty、JS、CSS
资源的模板模块,

// 调用模块的路径为 子系统名称:模板在 widget 目录下的路劲
{widget name="common:widget/nav/nav.tpl" }

这个模板模块(nav)目录下有与模板同名的 JS、CSS
文件,在模板被执行渲染时这些资源会被自动加载。如上所示,定义 template
模块的时候,只需要将 template 所依赖的 JS 模块、CSS
模块存放在同一目录(默认 JavaScript 模块、CSS 模块与 Template
模块同名)下即可,调用者调用 Template
模块只需要写一行代码即可,不需要关注所调用的 template
模块所依赖的静态资源,模板模块会帮助我们自动处理依赖关系以及资源加载。

性能优化方向分类

自动化编译

在前端开发中,我们总是会去使用很多工具、手段来优化代码、提升开发效率,例如,我们会使用sass、less等CSS预处理工具来编写更好维护的样式代码,我们也会使用CSSLint、eslint等代码检查工具来检查代码的语法错误,使用文件合并压缩等手段来减少资源大小,除此之外我们还会去做雪碧图合并、多倍图处理、字体压缩处理、代码发布等等。

曾经有大神说过,超过90s的工作都应该自动化掉。而以上所有的这些工作,贯穿我们整个开发流程,但是不同工具的切换不但显得凌乱,而且影响开发效率。在自动化、工程编译的思想早已深入人心的当下,我们当然也要紧跟潮流,所以我们考虑通过自动化手段来提升我们的效率,让所有操作可以一键式开速执行完。

我们将通过定义好一系列的编译任务,按照一定顺序依次对我们的项目自动进行编译操作,最后产生出可上线的代码。

前端开发领域(JavaScript、CSS、Template)并没有为开发者们提供以一种简洁、有条理地的方式来管理模块的方法。CommonJS(致力于设计、规划并标准化
JavaScript API)的诞生开启了“ JavaScript 模块化的时代”。CommonJS
的模块提案为在服务器端的 JavaScript
模块化做出了很大的贡献,但是在浏览器下的 JavaScript
模块应用很有限。随之而来又诞生了其它前端领域的模块化方案,像
requireJS、SeaJS 等,然而这些模块化方案并不是十分适用
,并没有从根本上解决模块化的问题。

实现一个页面功能总是需要 JavaScript、CSS 和 Template
三种语言相互组织,所以我们真正需要的是一种可以将 JavaScript、CSS 和
Template 同时都考虑进去的模块化方案。

把以上这些已经成熟应用到实际生产中的优化手段去除掉,留下那些还没有很好实现的优化原则。再来回顾一下之前的性能优化分类:

模块展示

模块展示,用于记录项目具体包含哪些模块以及模块具体的信息。在平常开发中,我们的项目会分为许多模块,不同的模块有不同的人来开发和维护。当项目越大的时候,可以通过管理平台清晰地看到模块具体的信息。

vns威尼斯城官网登入 12

前端模块化带来的性能问题

很多主流的模块化解决方案通过 JavaScript
运行时来支持“匿名闭包”、“依赖分析”和“模块加载”等功能,例如“依赖分析”需要在
JavaScript
运行时通过正则匹配到模块的依赖关系,然后顺着依赖链(也就是顺着模块声明的依赖层层进入,直到没有依赖为止)把所有需要加载的模块按顺序一一加载完毕,当模块很多、依赖关系复杂的情况下会严重影响页面性能。

CSS
Sprites
【在国内很多人叫css精灵,是一种网页图片应用处理方式。它允许你将一个页面涉及到的所有零星图片都包含到一张大图中去,这样一来,当访问该页面时,载入的图片就不会像以前那样一幅一幅地慢慢显示出来了。对于当前网络流行的速度而言,不高于200KB的单张图片的所需载入时间基本是差不多的,所以无需顾忌这个问题】

转载本站文章请注明出处:vns威尼斯城官网登入 http://www.tiec-ccpittj.com/?p=4272

上一篇:

下一篇:

相关文章