前端工程模块化——以一个php项目为例

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

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

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

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

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

首先我们来看一下一个 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 模块。下面我们来一一介绍这几种模块的模块化方案。

模板模块

我们可以将任何一段可复用的模板代码放到一个 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-{[email protected]}">
                <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 模块所依赖的静态资源,模板模块会帮助我们自动处理依赖关系以及资源加载。

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
 */

非模块化资源

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

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

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

项目实例

下面我们来看一下在一个实际项目中,如果在通过页面来调用各种类型的 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开源项目前端工程模块化: http://fex.baidu.com/blog/2014/03/fis-module/

时间: 2024-08-29 04:28:53

前端工程模块化——以一个php项目为例的相关文章

Ionic buid android下的此工程不是一个android项目问题

今天编译Ionic项目的时候报如下错误,甚是费解,之前一直都是好的 首先去检查了,相关JavaHome的环境变量,确定是好的,java -version 命令没有问题. 经查阅网上的解决方法,思路大都是添加或者删除相关的开发平台,顺着这个思路去试试看,果然有效 具体步骤: 1. cd yourProjectFolder 2.ionic platform rm android. 此时会把platforms/android目录下的所有文件全部删掉,但是仅仅这样做还是不够的,我们还需要整体手动将pla

[转载]前端工程——基础篇

特别声明:本文转载@云龙的<前端工程——基础篇>,感谢@云龙的分享. 喂喂喂,那个切图的,把页面写好就发给研发工程师套模板吧. 你好,切图仔. 不知道你的团队如何定义前端开发,据我所知,时至今日仍然有很多团队会把前端开发归类为产品或者设计岗位,虽然身份之争多少有些无谓,但我对这种偏见还是心存芥蒂,酝酿了许久,决定写一个系列的文章,试着从工程的角度系统的介绍一下我对前端,尤其是Web前端的理解. 只要我们还把自己的工作看作为一项软件开发活动,那么我相信读过下面的内容你也一定会有所共鸣. 前端,是

前端工程——基础篇

# 前端工程--基础篇 > 喂喂喂,那个切图的,把页面写好就发给研发工程师套模板吧. 你好,切图仔. 不知道你的团队如何定义前端开发,据我所知,时至今日仍然有很多团队会把前端开发归类为产品或者设计岗位,虽然身份之争多少有些无谓,但我对这种偏见还是心存芥蒂,酝酿了许久,决定写一个系列的文章,试着从工程的角度系统的介绍一下我对前端,尤其是Web前端的理解. 只要我们还把自己的工作看作为一项软件开发活动,那么我相信读过下面的内容你也一定会有所共鸣. ## 前端,是一种GUI软件 现如今前端可谓包罗万象

前端工程之模块化

模块化是一种处理复杂系统分解成为更好的可管理模块的方式,它可以把系统代码划分为一系列职责单一,高度解耦且可替换的模块,系统中某一部分的变化将如何影响其它部分就会变得显而易见,系统的可维护性更加简单易得. 前端开发领域(JavaScript.CSS.Template)并没有为开发者们提供以一种简洁.有条理地的方式来管理模块的方法.CommonJS(致力于设计.规划并标准化 JavaScript API)的诞生开启了“ JavaScript 模块化的时代”.CommonJS 的模块提案为在服务器端的

前端工程与模块化框架

and others Owner fouber commented on 14 Jun 2014 本文最先发表在 DIV.IO - 高质量前端社区,欢迎大家围观 不要再求验证码了,这个blog目前有800+人订阅,求验证没什么的很影响其他订阅者,可以在div.io上申请,定期会有同学发放的... 一直酝酿着写一篇关于模块化框架的文章,因为模块化框架是前端工程中的 最为核心的部分 .本来又想长篇大论的写一篇完整且严肃的paper,但看了 @糖饼 在 DIV.IO 的一篇文章 <再谈 SeaJS 与

仿B站项目——(1)计划,前端工程

计划 现打算: 计划用webpack打包 + 模板语言 + jquery + jquery ui + bootstrap做一个仿B站的静态网站. 网站兼容手机浏览器端. 部分模块打算仿照SPA用js加载的方式实现. 数据结构要有方便配置的形式.(便于网站更新) 网站优化,目前打算用图片懒加载等方法. 最终结果要与B站90%相同,包括动画,互动等,不包括用户登录,视屏播放等(因为要服务器支持). 后续打算: 网站用vue重构. 把网站做成一个webapp. 添加服务端. 前端工程 参考前端工程说明

读百度张云龙的前端工程有感

小标题:我刚写前端不到半年,学习速度比较快,也可以说比较浮躁,最近在研究node,想自己揽了全栈的活儿,偶然看到了一篇叫前端架构那些事儿的文章,于是跟着文章,我找到了张云龙先生的git,看了一下他对前端架构的看法,自认为深有感触,因此记下来,以告诉自己是有多么肤浅. 张云龙先生以切图仔引入,然而我并不怎么会切图,可以说我是十分业余的前端设计者,我只会敲敲代码,搭搭页面,写写动画,可以说我巧妙地避过了先生说的人们对前端的看法,想一想,“幸运”! “前端,是一种GUI软件”,“从本质上讲,所有Web

前端工程架构探讨

前端工程架构探讨 一.通常开发模式的问题探讨 下图是一个单页面多模块的工程目录结构图: . ├── Gruntfile.js ├── package.json ├── build └── src ├── base │ ├── base.sass │ └── global.js ├── mods │ ├── preference │ │ ├── index.js │ │ ├── index.sass │ │ └── index.xtpl.html │ ├── promo │ ├── qr │ └─

maven工程模块化

前言 项目的模块化有利于任务分工,后期维护,易扩展,模块还可以独立成服务单独部署等: 创建packaging类型为POM的父项目 我用的maven插件是m2e,相信大部分人在eclipse装的也是m2e插件:废话不说,直接开始: 菜单选择新建maven project,注意选择创建一个简单工程,如下图红圈所示,因为我们要创建的是packaging类型为pom的maven项目,自带的archetype里貌似没有对应的类型,反正我是没找到. 接着点击next,在packaging选项里选择pom,然