FIS 应用实例-require.js+CMD 模块
In 未分类 on 2015年05月13日 by view: 2,650
4

前面文章讲了 FIS 的源码实现细节,这篇文章偏实战一些,给出 FISrequire.js 结合的简单例子。

FIS 编译流程

如果已熟悉了 FIS 的编译设计,可以跳过这一节,直接进入下一小结。FIS 的编译主要有三步:

命令解析–> 资源编译–> 资源部署

  1. 资源编译:FIS 将文件资源抽象成 File 实例,该实例上有文件资源类型、id、内容、部署路径等的属性。对于文件的编译,实际上都是对 File 实例进行操作,比如修改资源的部署路径等(内存里操作)。
  2. 资源部署:根据 File 实例的属性,进行实际的部署动作(磁盘写操作)。

FIS 的这套编译体系,使得基于 FIS 的扩展相对比较容易。在扩展的同时,还可以确保编译的高性能。针对资源编译环节的扩展,除非是设计不合理,不然一般情况下不会导致性能的急剧降低。

getting started

啰嗦的讲了一大通,下面来点半干货。喜欢 require.js,但又喜欢用 CMD 编写模块的朋友有福了,下面会简单介绍如何整合 require.jsFIS

demo 已经放在 github,下载请猛戳

首先看下项目结构。modules 目录里的是模块化的资源,lib 目录里的是非模块化资源。其中:

  1. index.html 依赖 require.js 来实现模块化管理
  2. index.js 模块依赖 util.js 模块
  3. index.js、util.js 均采用 CMD 规范

也就是说,本例子主要实现的,就是 CMD 到 AMD 的转换。

资源概览

首先,我们看下 index.html,引用了 require.min.js,并加载了 modules/index 模块,跟着执行回调,没了。

接下来,我们看下 index.js。也很简单,加载依赖的模块 modules/util,接着暴露出本身模块,其实就是调用 Utill 模块的方法 deubg。

再看看 uti.js,不赘述。

如果换成熟悉的 AMD,index.js 应该是这样子的。那么思路就很清晰了。对 CMD 模块进行 define 包裹,同时将模块的依赖添加进去。

作为一枚贴近前端实践的集成解决方案,FIS 早已看穿一切。下面进入实战编码环节。

实战:修改 fis-conf.js

首先,打开 fis-conf.js,加入如下配置。配置大致意思是:

  1. postprocessor 环节,针对 js 文件,调用 fis-postprocessor-jswrapper 进行处理。
  2. postprocessor 插件的配置看 settings.postprocessortypeAMD,表示对模块进行 AMD 包裹。

接着,添加 roadmap.path 配置。直接看注释,如果对配置不熟悉,可参考官方文档

写在后面

本文简单介绍 CMD 到 AMD 的转换,距离实战还有很多事情要做,比如 require.js 的配置支持,打包部署等,这里也就抛个思路,感兴趣的童鞋可以进一步扩展。

文章: casperchen

FIS 源码-增量编译与依赖扫描细节
In 未分类 on 2015年05月11日 by view: 950
0

前面已经提到了 fis release 命令大致的运行流程。本文会进一步讲解增量编译以及依赖扫描的一些细节。

首先,在 fis release 后加上--watch 参数,看下会有什么样的变化。打开命令行

不难猜想,内部同样是调用 release()方法把源文件编译一遍。区别在于,进程会监听项目路径下源文件的变化,一旦出现文件(夹)的增、删、改,则重新调用 release()进行增量编译。

并且,如果资源之间存在依赖关系(比如资源内嵌),那么一些情况下,被依赖资源的变化,会反过来导致资源引用方的重新编译。

下面扒扒源码来验证下我们的猜想。

watch(opt) 细节

源码不算长,逻辑也比较清晰,这里就不上伪代码了,直接贴源码出来,附上一些注释,应该不难理解,无非就是重复文件变化–>release(opt)这个过程。

在下一小结稍稍展开下增量编译的细节。

增量编译细节

增量编译的要点很简单,就是只发生变化的文件进行编译部署。在 fis.release(opt, callback)里,有这段代码:

opt.afterEach(file, ret)这个回调方法可以在 fis-command-release/release.js 中找到。归纳下:

  1. 对比了下当前文件的最近修改时间,看下跟上次缓存的修改时间是否一致。如果不一致,重新编译,并将编译后的实例添加到 collection 中去。
  2. 执行 deploy 进行增量部署。(带着 collection 参数)

关于 deploy ,细节先略过,可以看到带上了 collection 参数。

依赖扫描概述

在增量编译的时候,有个细节点很关键,变化的文件,可能被其他资源所引用(如内嵌),那么这时,除了编译文件之身,还需要对引用它的文件也进行编译。

原先我的想法是:

  1. 扫描所有资源,并建立依赖分析表。比如某个文件,被多少文件引用了。
  2. 某个文件发生变化,扫描依赖分析表,对引用这个文件的文件进行重新编译。

看了下 FIS 的实现,虽然大体思路是一致的,不过是反向操作。从资源引用方作为起始点,递归式地对引用的资源进行编译,并添加到资源依赖表里。

  1. 扫描文件,看是否有资源依赖。如有,对依赖的资源进行编译,并添加到依赖表里。(递归)
  2. 编译文件。

从例子出发

假设项目结构如下,仅有 index.htmlindex.cc 两个文件,且 index.html 通过 __inline 标记嵌入 index.css

index.html 内容如下。

假设文件内容发生了变化,理论上应该是这样

  1. index.html 变化:重新编译 index.html
  2. index.css 变化:重新编译 index.css,重新编译 index.html

理论是直观的,那么看下内部是怎么实现这个逻辑的。先归纳如下,再看源码

  1. 对需要编译的每个源文件,都创建一个 Cache 实例,假设是 cache。cache 里存放了一些信息,比如文件的内容,文件的依赖列表 (deps 字段,一个哈希表,存放依赖文件路径到最近修改时间的映射)。
  2. 对需要编译的每个源文件,扫描它的依赖,包括通过__inline 内嵌的资源,并通过 cache.addDeps(file)添加到 deps 里。
  3. 文件发生变化,检查文件本身内容,以及依赖内容 (deps) 是否发生变化。如变化,则重新编译。在这个例子里,扫描 index.html,发现 index.html 本身没有变化,但 deps 发生了变化,那么,重新编译部署 index.html

好,看源码。在 compile.js 里面,cache.revert(revertObj)这个方法检测文件本身、文件依赖的资源是否变化。

看看 cache.revert 是如何定义的。大致归纳如下,源码不难看懂。至于 infos.deps 这货怎么来的,下面会立刻讲到。

  1. 方法的返回值:缓存没过期,返回 true;缓存过期,返回 false
  2. 缓存检查步骤:首先,检查文件本身是否发生变化,如果没有,再检查文件依赖的资源是否发生变化;

依赖扫描细节

之前多次提到 deps 这货,这里就简单讲下依赖扫描的过程。还是之前 compile.js 里那段代码。归纳如下:

  1. 文件缓存不存在,或者文件缓存已过期,进入第二个处理分支
  2. 在第二个处理分支里,会调用 process(file)这个方法对文件进行处理。里面进行了一系列操作,如文件的 “标准化” 处理等。在这个过程中,扫描出文件的依赖,并写到 deps 里去。

下面会以 “标准化” 为例,进一步讲解依赖扫描的过程。

process 里,对文件进行了标准化操作。什么是标准化,可以参考官方文档。就是下面这小段代码

看下 standard 内部是如何实现的。可以看到,针对类 HTML、类 JS、类 CSS,分别进行了不同的能力扩展(包括内嵌)。比如上面的 index.html,就会进入 extHtml(content)。这个方法会扫描 html 文件的__inline 标记,然后替换成特定的占位符,并将内嵌的资源加入依赖列表。

比如,文件的<link href="index.css?__inline" /> 会被替换成 <style type="text/css"><<<embed:"index.css?__inline">>>

然后,在 content.replace 里面,将进入 embed 这个分支。从源码可以大致看出逻辑如下,更多细节就先不展开了。

  1. 首先对内嵌的资源进行合法性检查,如果通过,进行下一步
  2. 编译内嵌的资源。(一个递归的过程)
  3. 将内嵌的资源加到依赖列表里。

写在后面

更多内容,敬请期待。

文章: casperchen

FIS 源码-fisrelease 概览
In 未分类 on 2015年05月08日 by view: 592
0

前面已经已 fis server open 为例,讲解了 FIS 的整体架构设计,以及命令解析&执行的过程。下面就进入 FIS 最核心的部分,看看执行 fis release 这个命令时,FIS 内部的代码逻辑。

这一看不打紧,基本把 fis-kernel 的核心模块翻了个遍,虽然大部分细节已经在脑海里里,但是要完整清晰的写出来不容易。于是决定放弃大而全的篇幅,先来个概要的分析,后续文章再针对涉及的各个环节的细节进行展开。

看看 fis-command-release

老规矩,献上精简版的 release.js,从函数名就大致知道干嘛的。release(options)是我们重点关注的对象。

release(options); 做了些什么

用伪代码将逻辑抽象下,主要分为四个步骤。虽然最后一步才是本片文章想要重点讲述的,不过前三步是第四步的基础,所以这里还是花点篇幅介绍下。

下面简单对上面几个步骤进行一一讲解。

findFisConf() + setProjectRoot()

由于这两步之间存在比较紧密的联系,所以这里就放一起讲。在没有任何运行参数的情况下,比较简单

  1. 从命令运行时所在的工作目录开始,向上逐级查找 fis-conf.js,直到找到位置
  2. 如果找到 fis-conf.js,则以它为项目配置文件。同时,将项目的根路径设置为 fis-conf.js 所在的目录。
  3. 如果没有找到 fis-conf.js,则采用默认项目配置。同时,将项目的根路径,设置为当前命令运行时所在的工作目录。

fis release 的支持的配置参数可以知道,可以分别通过:

  1. --file:指定 fis-conf.js 的路径(比如多个项目公用编译配置)
  2. --root:指定项目根路径(在 A 工作目录,编译 B 工作目录)

由本小节前面的介绍得知,--file--root 两个配置参数之间是存在联系的,有可能同时存在。下面用伪代码来说明下

mergeFisConf()

合并项目配置文件。从源码可以清楚的看到,包含两个步骤:

  1. fis-conf.js 创建缓存。除了配置文件,FIS 还会为项目的所有源文件建立缓存,实现增量编译,加快编译速度。缓存的细节后面再讲,这里知道有这么回事就行。
  2. 合并项目自定义配置

readSourcesAndReleaseToDest()

通过这个死长的伪函数名,就知道这个步骤的作用了,非常关键。根据当前项目配置,读取项目的源文件,编译后输出到目标目录。

编译过程的细节,下一节会讲到。

项目编译大致流程

项目编译发布的细节,主要是在 release 这个方法里完成。细节非常的多,主要在 fis.release()这个调用里完成,基本上用到了 fis-kernel 里所有的模块,如 releasecompilecache 等。

  1. 读取项目源文件,并将每个源文件抽象为一个 File 实例。
  2. 读取项目配置,并根据项目配置,初始化 File 实例。
  3. 为 File 实例建立编译缓存,提高编译速度。
  4. 根据文件类型、配置等编译源文件。(File 实例各种属性的修改)
  5. 项目部署:将编译结果实际写到本地磁盘。

伪代码流程如下:fis-command-release/release.js

至于 fis.release()

前面说了,细节非常多,后续文章继续展开。。。

文章: casperchen

FIS 源码解析-整体架构
In 未分类 on 2015年05月08日 by view: 1,368
0

序言

这里假设本文读者对 FIS 已经比较熟悉,如还不了解,可猛击官方文档

虽然 FIS 整体的源码结构比较清晰,不过讲解起来也是个系统庞大的工程,笔者尽量的挑重点的讲。如果读者有感兴趣的部分笔者没有提到的,或者是存在疑惑的,可以在评论里跑出来,笔者会试着去覆盖这些点。

下笔匆忙,如有错漏请指出。

Getting started

如在开始剖析 FIS 的源码前,有三点内容首先强调下,这也是解构 FIS 内部设计的基础。

1、 FIS 支持三个命令,分别是 fis releasefis serverfis install。当用户输入 fis xx 的时候,内部调用 fis-command-releasefis-command-serverfis-command-install 这三个插件来完成任务。同时,FIS 的命令行基于 commander 这个插件构建,熟悉这个插件的同学很容易看懂 FIS 命令行相关部分源码。

2、FIS 以 fis-kernel 为核心。fis-kernel 提供了 FIS 的底层能力,包含了一系列模块,如配置、缓存、文件处理、日志等。FIS 的三个命令,最终调用了这些模块来完成构建的任务。参考 fis-kernel/lib/ 目录,下面对每个模块的大致作用做了简单备注,后面的文章再详细展开。

3、FIS 的编译过程,最终可以拆解为细粒度的单文件编译,理解了下面这张图,对于阅读 FIS 的源码有非常大的帮助。(主要是 fis release 这个命令)

enter image description here

一个简单的例子:fis server open

开篇的描述可能比较抽象,下面我们来个实际的例子。通过这个简单的例子,我们可以对 FIS 的整体设计有个大致的印象。

下文以 fis server open 为例,逐步剖析 FIS 的整体设计。其实 FIS 比较精华的部分集中在 fis release 这个命令,不过 fis server 这个命令相对简单,更有助于我们从纷繁的细节中跳出来,窥探 FIS 的整体概貌。

假设我们已经安装了 FIS。好,打开控制台,输入下面命令,其实就是打开 FIS 的 server 目录

package.json 可以知道,此时调用了 fis/bin/fis,里面只有一行有效代码,调用 fis.cli.run()方法,同时将进程参数传进去。

接下来看下../fis.js。代码结构非常清晰。注意,笔者将一些代码给去掉,避免长串的代码影响理解。同时在关键处加了简单的注释

我们来看下笔者注释过的 fis.cli.run 的源码。

  1. 如果是 fis -h 或者 fis --help,打印帮助信息
  2. 如果是 fis -v 或者 fis --version,打印版本信息
  3. 其他情况:加载相关命令对应的插件,并执行命令,比如 fis-command-server

通过 fis.cli.run 的源码,我们可以看到,fis-command-xx 插件,都提供了 register 方法,在这个方法内完成命令的初始化。之后,通过 commander.parse(argv)来执行命令。

整个流程归纳如下:

  1. 用户输入 FIS 命令,如 fis server open
  2. 解析命令,根据指令加载对应插件,如 fis-command-server
  3. 执行命令

fis-command-server 源码

三个命令相关的插件中,fis-command-server 的代码比较简单,这里就通过它来大致介绍下。

根据惯例,同样是抽取一个超级精简版的 fis-command-server,这不影响我们对源码的理解

好了,fis server open 就大致剖析到这里。只要熟悉 commander 这个插件,相信不难看懂上面的代码,这里就不多做展开了,有空也写篇科普文讲下 commander 的使用。

写在后面

如序言所说,欢迎交流探讨。如有错漏,请指出。

文章: casperchen