We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
前面已经提到了fis release命令大致的运行流程。本文会进一步讲解增量编译以及依赖扫描的一些细节。
fis release
首先,在fis release后加上--watch参数,看下会有什么样的变化。打开命令行
--watch
fis release --watch
不难猜想,内部同样是调用release()方法把源文件编译一遍。区别在于,进程会监听项目路径下源文件的变化,一旦出现文件(夹)的增、删、改,则重新调用release()进行增量编译。
release()
并且,如果资源之间存在依赖关系(比如资源内嵌),那么一些情况下,被依赖资源的变化,会反过来导致资源引用方的重新编译。
// 是否自动重新编译 if(options.watch){ watch(options); // 对!就是这里 } else { release(options); }
下面扒扒源码来验证下我们的猜想。
源码不算长,逻辑也比较清晰,这里就不上伪代码了,直接贴源码出来,附上一些注释,应该不难理解,无非就是重复**文件变化-->release(opt)**这个过程。
在下一小结稍稍展开下增量编译的细节。
function watch(opt){ var root = fis.project.getProjectPath(); var timer = -1; var safePathReg = /[\\\/][_\-.\s\w]+$/i; // 是否安全路径(参考) var ignoredReg = /[\/\\](?:output\b[^\/\\]*([\/\\]|$)|\.|fis-conf\.js$)/i; // ouput路径下的,或者 fis-conf.js 排除,不参与监听 opt.srcCache = fis.project.getSource(); // 缓存映射表,代表参与编译的源文件;格式为 源文件路径=>源文件对应的File实例。比较奇怪的是,opt.srcCache 没见到有地方用到,在 fis.release 里,fis.project.getSource() 会重新调用,这里感觉有点多余 // 根据传入的事件类型(type),返回对应的回调方法 // type 的取值有add、change、unlink、unlinkDir function listener(type){ return function (path) { if(safePathReg.test(path)){ var file = fis.file.wrap(path); if (type == 'add' || type == 'change') { // 新增 或 修改文件 if (!opt.srcCache[file.subpath]) { // 新增的文件,还不在 opt.srcCache 里 var file = fis.file(path); opt.srcCache[file.subpath] = file; // 从这里可以知道 opt.srcCache 的数据结构了,不展开 } } else if (type == 'unlink') { // 删除文件 if (opt.srcCache[file.subpath]) { delete opt.srcCache[file.subpath]; // } } else if (type == 'unlinkDir') { // 删除目录 fis.util.map(opt.srcCache, function (subpath, file) { if (file.realpath.indexOf(path) !== -1) { delete opt.srcCache[subpath]; } }); } clearTimeout(timer); timer = setTimeout(function(){ release(opt); // 编译,增量编译的细节在内部实现了 }, 500); } }; } //添加usePolling配置 // 这个配置项可以先忽略 var usePolling = null; if (typeof fis.config.get('project.watch.usePolling') !== 'undefined'){ usePolling = fis.config.get('project.watch.usePolling'); } // chokidar模块,主要负责文件变化的监听 // 除了error之外的所有事件,包括add、change、unlink、unlinkDir,都调用 listenter(eventType) 来处理 require('chokidar') .watch(root, { // 当文件发生变化时候,会调用这个方法(参数是变化文件的路径) // 如果返回true,则不触发文件变化相关的事件 ignored : function(path){ var ignored = ignoredReg.test(path); // 如果满足,则忽略 // 从编译队列中排除 if (fis.config.get('project.exclude')){ ignored = ignored || fis.util.filter(path, fis.config.get('project.exclude')); // 此时 ignoredReg.test(path) 为false,如果在exclude里,ignored也为true } // 从watch中排除 if (fis.config.get('project.watch.exclude')){ ignored = ignored || fis.util.filter(path, fis.config.get('project.watch.exclude')); // 跟上面类似 } return ignored; }, usePolling: usePolling, persistent: true }) .on('add', listener('add')) .on('change', listener('change')) .on('unlink', listener('unlink')) .on('unlinkDir', listener('unlinkDir')) .on('error', function(err){ //fis.log.error(err); }); }
增量编译的要点很简单,就是只发生变化的文件进行编译部署。在fis.release(opt, callback)里,有这段代码:
fis.release(opt, callback)
// ret.src 为项目下的源文件 fis.util.map(ret.src, function(subpath, file){ if(opt.beforeEach) { opt.beforeEach(file, ret); } file = fis.compile(file); if(opt.afterEach) { opt.afterEach(file, ret); // 这里这里! }
opt.afterEach(file, ret)这个回调方法可以在 fis-command-release/release.js 中找到。归纳下:
opt.afterEach(file, ret)
fis-command-release/release.js
collection
deploy
opt.afterEach = function(file){ //cal compile time // 略过无关代码 var mtime = file.getMtime().getTime(); // 源文件的最近修改时间 //collect file to deploy // 如果符合这几个条件:1、文件需要部署 2、最近修改时间 不等于 上一次缓存的修改时间 // 那么重新编译部署 if(file.release && lastModified[file.subpath] !== mtime){ // 略过无关代码 lastModified[file.subpath] = mtime; collection[file.subpath] = file; // 这里这里!!在 deploy 方法里会用到 } };
关于deploy ,细节先略过,可以看到带上了collection参数。
deploy(opt, collection, total); // 部署~
在增量编译的时候,有个细节点很关键,变化的文件,可能被其他资源所引用(如内嵌),那么这时,除了编译文件之身,还需要对引用它的文件也进行编译。
原先我的想法是:
看了下FIS的实现,虽然大体思路是一致的,不过是反向操作。从资源引用方作为起始点,递归式地对引用的资源进行编译,并添加到资源依赖表里。
假设项目结构如下,仅有index.html、index.cc两个文件,且 index.html 通过 __inline 标记嵌入 index.css。
index.html
index.cc
__inline
index.css
^CadeMacBook-Pro-3:fi a$ tree . ├── index.css └── index.html
index.html 内容如下。
<!DOCTYPE html> <html> <head> <title></title> <link rel="stylesheet" type="text/css" href="index.css?__inline"> </head> <body> </body> </html>
假设文件内容发生了变化,理论上应该是这样
理论是直观的,那么看下内部是怎么实现这个逻辑的。先归纳如下,再看源码
cache.addDeps(file)
deps
好,看源码。在compile.js里面,cache.revert(revertObj)这个方法检测文件本身、文件依赖的资源是否变化。
compile.js
cache.revert(revertObj)
if(file.isFile()){ if(file.useCompile && file.ext && file.ext !== '.'){ var cache = file.cache = fis.cache(file.realpath, CACHE_DIR), // 为文件建立缓存(路径) revertObj = {}; // 目测是检测缓存过期了没,如果只是跑 fis release ,直接进else if(file.useCache && cache.revert(revertObj)){ // 检查依赖的资源(deps)是否发生变化,就在 cache.revert(revertObj)这个方法里 exports.settings.beforeCacheRevert(file); file.requires = revertObj.info.requires; file.extras = revertObj.info.extras; if(file.isText()){ revertObj.content = revertObj.content.toString('utf8'); } file.setContent(revertObj.content); exports.settings.afterCacheRevert(file); } else {
看看cache.revert是如何定义的。大致归纳如下,源码不难看懂。至于infos.deps这货怎么来的,下面会立刻讲到。
cache.revert
infos.deps
// 如果过期,返回false;没有过期,返回true // 注意,穿进来的file对象会被修改,往上挂属性 revert : function(file){ fis.log.debug('revert cache'); // this.cacheInfo、this.cacheFile 中存储了文件缓存相关的信息 // 如果还不存在,说明缓存还没建立哪(或者被人工删除了也有可能,这种变态情况不多) if( exports.enable && fis.util.exists(this.cacheInfo) && fis.util.exists(this.cacheFile) ){ fis.log.debug('cache file exists'); var infos = fis.util.readJSON(this.cacheInfo); fis.log.debug('cache info read'); // 首先,检测文件本身是否发生变化 if(infos.version == this.version && infos.timestamp == this.timestamp){ // 接着,检测文件依赖的资源是否发生变化 // infos.deps 这货怎么来的,可以看下compile.js 里的实现 var deps = infos['deps']; for(var f in deps){ if(deps.hasOwnProperty(f)){ var d = fis.util.mtime(f); if(d == 0 || deps[f] != d.getTime()){ // 过期啦!! fis.log.debug('cache is expired'); return false; } } } this.deps = deps; fis.log.debug('cache is valid'); if(file){ file.info = infos.info; file.content = fis.util.fs.readFileSync(this.cacheFile); } fis.log.debug('revert cache finished'); return true; } } fis.log.debug('cache is expired'); return false; },
之前多次提到deps这货,这里就简单讲下依赖扫描的过程。还是之前compile.js里那段代码。归纳如下:
process(file)
下面会以“标准化”为例,进一步讲解依赖扫描的过程。
if(file.useCompile && file.ext && file.ext !== '.'){ var cache = file.cache = fis.cache(file.realpath, CACHE_DIR), // 为文件建立缓存(路径) revertObj = {}; // 目测是检测缓存过期了没,如果只是跑 fis release ,直接进else if(file.useCache && cache.revert(revertObj)){ exports.settings.beforeCacheRevert(file); file.requires = revertObj.info.requires; file.extras = revertObj.info.extras; if(file.isText()){ revertObj.content = revertObj.content.toString('utf8'); } file.setContent(revertObj.content); exports.settings.afterCacheRevert(file); } else { // 缓存过期啦!!缓存还不存在啊!都到这里面来!! exports.settings.beforeCompile(file); file.setContent(fis.util.read(file.realpath)); process(file); // 这里面会对文件进行"标准化"等处理 exports.settings.afterCompile(file); revertObj = { requires : file.requires, extras : file.extras }; cache.save(file.getContent(), revertObj); } }
在process里,对文件进行了标准化操作。什么是标准化,可以参考官方文档。就是下面这小段代码
process
if(file.useStandard !== false){ standard(file); }
看下standard内部是如何实现的。可以看到,针对类HTML、类JS、类CSS,分别进行了不同的能力扩展(包括内嵌)。比如上面的index.html,就会进入extHtml(content)。这个方法会扫描html文件的__inline标记,然后替换成特定的占位符,并将内嵌的资源加入依赖列表。
standard
extHtml(content)
比如,文件的<link href="index.css?__inline" />会被替换成 <style type="text/css"><<<embed:"index.css?__inline">>>。
<link href="index.css?__inline" />
<style type="text/css"><<<embed:"index.css?__inline">>>
function standard(file){ var path = file.realpath, content = file.getContent(); if(typeof content === 'string'){ fis.log.debug('standard start'); //expand language ability if(file.isHtmlLike){ content = extHtml(content); // 如果有 <link href="index1.css?__inline" /> 会被替换成 <style type="text/css"><<<embed:"index1.css?__inline">>> 这样的占位符 } else if(file.isJsLike){ content = extJs(content); } else if(file.isCssLike){ content = extCss(content); } content = content.replace(map.reg, function(all, type, value){ // 虽然这里很重要,还是先省略代码很多很多行 } }
然后,在content.replace里面,将进入embed这个分支。从源码可以大致看出逻辑如下,更多细节就先不展开了。
content.replace
embed
content = content.replace(map.reg, function(all, type, value){ var ret = '', info; try { switch(type){ case 'require': // 省略... case 'uri': // 省略... case 'dep': // 省略 case 'embed': case 'jsEmbed': info = fis.uri(value, file.dirname); // value ==> ""index.css?__inline"" var f; if(info.file){ f = info.file; } else if(fis.util.isAbsolute(info.rest)){ f = fis.file(info.rest); } if(f && f.isFile()){ if(embeddedCheck(file, f)){ // 一切合法性检查,比如有没有循环引用之类的 exports(f); // 编译依赖的资源 addDeps(file, f); // 添加到依赖列表 f.requires.forEach(function(id){ file.addRequire(id); }); if(f.isText()){ ret = f.getContent(); if(type === 'jsEmbed' && !f.isJsLike && !f.isJsonLike){ ret = JSON.stringify(ret); } } else { ret = info.quote + f.getBase64() + info.quote; } } } else { fis.log.error('unable to embed non-existent file [' + value + ']'); } break; default : fis.log.error('unsupported fis language tag [' + type + ']'); } } catch (e) { embeddedMap = {}; e.message = e.message + ' in [' + file.subpath + ']'; throw e; } return ret; });
更多内容,敬请期待。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
开篇
前面已经提到了
fis release
命令大致的运行流程。本文会进一步讲解增量编译以及依赖扫描的一些细节。首先,在
fis release
后加上--watch
参数,看下会有什么样的变化。打开命令行不难猜想,内部同样是调用
release()
方法把源文件编译一遍。区别在于,进程会监听项目路径下源文件的变化,一旦出现文件(夹)的增、删、改,则重新调用release()
进行增量编译。并且,如果资源之间存在依赖关系(比如资源内嵌),那么一些情况下,被依赖资源的变化,会反过来导致资源引用方的重新编译。
下面扒扒源码来验证下我们的猜想。
watch(opt)细节
源码不算长,逻辑也比较清晰,这里就不上伪代码了,直接贴源码出来,附上一些注释,应该不难理解,无非就是重复**文件变化-->release(opt)**这个过程。
在下一小结稍稍展开下增量编译的细节。
增量编译细节
增量编译的要点很简单,就是只发生变化的文件进行编译部署。在
fis.release(opt, callback)
里,有这段代码:opt.afterEach(file, ret)
这个回调方法可以在fis-command-release/release.js
中找到。归纳下:collection
中去。deploy
进行增量部署。(带着collection参数)关于
deploy
,细节先略过,可以看到带上了collection
参数。依赖扫描概述
在增量编译的时候,有个细节点很关键,变化的文件,可能被其他资源所引用(如内嵌),那么这时,除了编译文件之身,还需要对引用它的文件也进行编译。
原先我的想法是:
看了下FIS的实现,虽然大体思路是一致的,不过是反向操作。从资源引用方作为起始点,递归式地对引用的资源进行编译,并添加到资源依赖表里。
从例子出发
假设项目结构如下,仅有
index.html
、index.cc
两个文件,且index.html
通过__inline
标记嵌入index.css
。index.html
内容如下。假设文件内容发生了变化,理论上应该是这样
理论是直观的,那么看下内部是怎么实现这个逻辑的。先归纳如下,再看源码
__inline
内嵌的资源,并通过cache.addDeps(file)
添加到deps
里。index.html
,发现index.html
本身没有变化,但deps
发生了变化,那么,重新编译部署index.html
。好,看源码。在
compile.js
里面,cache.revert(revertObj)
这个方法检测文件本身、文件依赖的资源是否变化。看看
cache.revert
是如何定义的。大致归纳如下,源码不难看懂。至于infos.deps
这货怎么来的,下面会立刻讲到。依赖扫描细节
之前多次提到
deps
这货,这里就简单讲下依赖扫描的过程。还是之前compile.js
里那段代码。归纳如下: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
这个分支。从源码可以大致看出逻辑如下,更多细节就先不展开了。写在后面
更多内容,敬请期待。
The text was updated successfully, but these errors were encountered: