web前端开发规范文档|前端和游戏开发哪个好学|前端开发浏览器兼容问题
模块化是一种处理复杂系统分解成为更好的可管理模块的方式,它可以把系统代码划分为一系列职责单一,高度解耦且可替换的模块,系统中某一部分的变化将如何影响其它部分就会变得显而易见,系统的可维护性更加简单易得。
前端开发领域(JavaScript、css、Template)并没有为开发者们提供以一种简洁、有条理地的方式来管理模块的方法。CommonJS(致力于设计、规划并标准化 JavaScript API)的诞生开启了“ JavaScript 模块化的时代”。CommonJS 的模块提案为在服务器端的 JavaScript 模块化做出了很大的贡献,但是在浏览器下的 JavaScript 模块应用很有限。随之而来又诞生了其它前端领域的模块化方案,像 requireJS、SeaJS等,然而这些模块化方案并不是十分适用 ,并没有从根本上解决模块化的问题。
1 前端模块化不等于js模块化
前端开发相对其他语言来说比较特殊,因为我们实现一个页面功能总是需要 JavaScript、css 和 Template 三种语言相互组织才行,如果一个功能仅仅只有 JavaScript 实现了模块化,css 和 Template 还是处于原始状态,那我们调用这个功能的时候并不能完全通过模块化的方式,那么这样的模块化方案并不是完整的,所以我们真正需要的是一种可以将 JavaScript、css 和 Template 同时都考虑进去的模块化方案,而非仅仅 JavaScript 模块化方案。
2 js模块化不等于异步模块化
主流的 JavaScript 模块化方案都使用“异步模块定义”的方式,这种方式给开发带来了极大的不便,所有的同步代码都需要修改为异步的方式,我们是否可以在前端开发中使用“ CommonJS ”的方式,开发者可以使用自然、容易理解的模块定义和调用方式,不需要关注模块是否异步,不需要改变开发者的开发行为。
3 前端模块化带来的性能问题
很多主流的模块化解决方案通过 JavaScript 运行时来支持“匿名闭包”、“依赖分析”和“模块加载”等功能,例如“依赖分析”需要在 JavaScript 运行时通过正则匹配到模块的依赖关系,然后顺着依赖链(也就是顺着模块声明的依赖层层进入,直到没有依赖为止)把所有需要加载的模块按顺序一一加载完毕,当模块很多、依赖关系复杂的情况下会严重影响页面性能。
4 模块化为打包部署带来极大不便
传统的模块化方案更多的考虑是如何将代码进行拆分,但是当我们部署上线的时候需要将静态资源进行合并(打包),这个时候会发现困难重重,每个文件里只能有一个模块,因为模块使用的是“匿名定义”,经过一番研究,我们会发现一些解决方案,无论是“ combo 插件”还是“ flush 插件”,都需要我们修改模块化调用的代码,这无疑是雪上加霜,开发者不仅仅需要在本地开发关注模块化的拆分,在调用的时候还需要关注在一个请求里面加载哪些模块比较合适,模块化的初衷是为了提高开发效率、降低维护成本,但我们发现这样的模块化方案实际上并没有降低维护成本,某种程度上来说使得整个项目更加复杂了。
5 一体化前端模块化实践方案
写到这里,其实我们的“前端工程之块化”才正式开始,本文面向对前端模块化开发有所实践或有所研究的同学,接下来我们所介绍的前端模块化解决方案, 有别于 JavaScript 模块化方案或 css 模块化方案,它是一种可以综合处理前端各种资源的模块化方案;它可以极大提升开发者的开发体验,并为性能优化提供良好的支持。下面让我们来进一步来了解什么是“一体化”的模块化实践方案。
首先我们来看一下一个 web 项目是如何通过“一体化”的模块化方案来划分目录结构:
站点(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 模块。下面我们来一一介绍这几种模块的模块化方案。
6 模板模块
我们可以将任何一段可复用的模板代码放到一个 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><span>{$doc.title}</span>
</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 模块所依赖的静态资源,模板模块会帮助我们自动处理依赖关系以及资源加载。
7 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 用于处理首屏外的按需模块。
8 CSS 模块
在模板模块中以及 JS 模块中对应同名的 CSS 模块会自动与模板模块、JS 模块添加依赖关系,进行加载管理,用户不需要显示进行调用加载。那么如何在一个 CSS 模块中声明对另一个 CSS 模块的依赖关系呢,我们可以通过在注释中的@require 字段标记的依赖关系,这些分析处理对 html 的 style 标签内容同样有效,
/**
- demo.css
- @require reset.css
*/
9 非模块化资源
在实际开发过程中可能存在一些不适合做模块化的静态资源,那么我们依然可以通过声明依赖关系来托管给静态资源管理系统来统一管理和加载,
{require name=”home:static/index/index.css” }
如果通过如上语法可以在页面声明对一个非模块化资源的依赖,在页面运行时可以自动加载相关资源。
10 项目实例
下面我们来看一下在一个实际项目中,如果在通过页面来调用各种类型的 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 等多种模块,我们再调用的时候只需要一行代码就可以了,并不需要关注内部的依赖,以及各种模块的初始化问题。
11 模块化基础架构
总体架构
为了实现一种自然、便捷、高性能、一体化的模块化方案,我们需要解决以下一些问题,
模块静态资源管理,一般模块总会包含 JavaScript、CSS 等其他静态资源,需要记录与管理这些静态资源
模块依赖关系处理,模块间存在各种依赖关系,在加载模块的时候需要处理好这些依赖关系
模块加载,在模块初始化之前需要将模块的静态资源以及所依赖的模块加载并准备好
模块沙箱(模块闭包),在 JavaScript 模块中我们需要自动对模块添加闭包用于解决作用域问题
使用编译工具来管理模块
我们可以通过编译工具(自动化工具) 对模块进行编译处理,包括对静态资源进行预处理(对 JavaScript 模块添加闭包、对 CSS 进行 LESS 预处理等)、记录每个静态资源的部署路径以及依赖关系并生成资源表(resource map)。我们可以通过编译工具来托管所有的静态资源,这样可以帮我们解决模块静态资源管理、模块依赖关系、模块沙箱问题。
使用静态资源加载框架来加载模块
那么如何解决模块加载问题,我们可以通过静态资源加载框架来解决,主要包含前端模块加载框架,用于 JavaScript 模块化支持,控制资源的异步加载。后端模块化框架,用于解决 JavaScript 同步加载、CSS 和模板等模块资源的加载,静态资源加载框架可以用于对页面进行持续的自适应的前端性能优化,自动对页面的不同情况投递不同的资源加载方案,帮助开发者管理静态资源,抹平本地开发到部署上线的性能沟壑。 编译工具和静态资源加载框架的流程图如下:
编译工具
自动化工具会扫描目录下的模块进行编译处理并输出产出文件:
静态资源,经过编译处理过的 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,然后组织资源输出最终页面。
静态资源加载框架
下面我们会详细讲解如何加载模块,如下所示,
在流程开始前我们需要准备两个数据结构:
uris = [],数组,顺序存放要输出资源的 uri
has = {},hash 表,存放已收集的静态资源,防止重复加载
1、加载资源表(resource map):
javascript { “res”: { “A/A.tpl”: { “uri”: “/templates/A.tpl”, “deps”: [“A/A.css”] }, “A/A.css”: { “uri”: “/static/css/A_7defa41.css” }, “B/B.tpl”: { “uri”: “/templates/B.tpl”, “deps”: [“B/B.css”] }, “B/B.css”: { “uri”: “/static/css/B_33c5143.css” }, “C/C.tpl”: { “uri”: “/templates/C.tpl”, “deps”: [“C/C.css”] }, “C/C.css”: { “uri”: “/static/css/C_6a59c31.css” } } }
2、执行 {widget name=”A”}
在表中查找 id 为 A/A.tpl 的资源,取得它的资源路径 /template/A.tpl,记为 tpl_path,加载并渲染 tplpath 所指向的模板文件,即 /template/A.tpl,并输出它的 html 内容
查看 A/A.tpl 资源的 deps 属性,发现它依赖资源 A/A.css,在表中查找 id 为 A/A.css 的资源,取得它的资源路径为 /static/css/A7defa41.css,存入 uris 数组 中,并在 has 表 里标记已加载 A/A.css 资源,我们得到:
javascript urls = [ ‘/static/css/A_7defa41.css’ ]; has = { “A/A.css”: true }
3、依次执行 {widget name=”B”}、{widget name=”c”},步骤与上述步骤 3 相同,得到,
“`javascript urls = [ ‘/static/css/A_7defa41.css’, ‘/static/css/B_33c5143.css’, ‘/static/css/C_6a59c31.css’ ];
has = { “A/A.css”: true, “B/B.css”: true, “C/C.css”: true }
评论前必须登录!
注册