├── .gitignore ├── test ├── raw │ └── src │ │ ├── empty │ │ ├── css.css │ │ └── js.js │ │ ├── js │ │ ├── change.js │ │ └── hello.js │ │ ├── project │ │ ├── file1.js │ │ ├── file2.js │ │ └── file3.js │ │ ├── change │ │ └── change.js │ │ ├── css │ │ └── css.css │ │ ├── cwd │ │ └── all │ │ │ ├── bar.js │ │ │ ├── foo.js │ │ │ └── zoo.js │ │ ├── dir │ │ ├── foo.js │ │ └── glob │ │ │ ├── bar.js │ │ │ └── zoo.js │ │ ├── glob │ │ ├── foo.js │ │ └── dir │ │ │ └── bar.js │ │ ├── plugins │ │ └── foo.js │ │ ├── single │ │ └── foo.js │ │ ├── temp │ │ └── foo.js │ │ ├── deleteFile │ │ ├── foo.js │ │ └── concat │ │ │ └── bar.js │ │ ├── readDir │ │ ├── bar.js │ │ └── foo.js │ │ ├── rename │ │ └── base.js │ │ ├── dependencyFile │ │ ├── foo.js │ │ ├── dependency_a.js │ │ ├── dependency_b.js │ │ └── dependency_c.js │ │ ├── overReferences │ │ └── foo.js │ │ ├── duplicateDefinition │ │ └── foo.js │ │ └── less │ │ ├── style.less │ │ └── fn.less ├── bone │ ├── callback_error.js │ ├── dependency.js │ ├── dependency_array.js │ ├── plugins_author.js │ ├── plugins_copyright.js │ └── bonefile.js ├── log.js ├── cache.js ├── plugins.js ├── utils.js └── index.js ├── index.js ├── bone.png ├── docs ├── cli.md ├── getting-started.md ├── plugin.md ├── file.md └── api.md ├── .travis.yml ├── lib ├── data.js ├── kernel.js ├── log.js ├── cache.js ├── watch.js ├── utils.js ├── plugins.js ├── read_stream.js ├── file.js └── fs.js ├── Makefile ├── log.md ├── package.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /test/raw/src/empty/css.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/raw/src/empty/js.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/raw/src/js/change.js: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /test/raw/src/project/file1.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/raw/src/project/file2.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/raw/src/project/file3.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/raw/src/change/change.js: -------------------------------------------------------------------------------- 1 | change file -------------------------------------------------------------------------------- /test/raw/src/css/css.css: -------------------------------------------------------------------------------- 1 | body {background: white} -------------------------------------------------------------------------------- /test/raw/src/js/hello.js: -------------------------------------------------------------------------------- 1 | alert('hello world!'); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/kernel.js'); -------------------------------------------------------------------------------- /test/raw/src/cwd/all/bar.js: -------------------------------------------------------------------------------- 1 | console.log('hello world') -------------------------------------------------------------------------------- /test/raw/src/cwd/all/foo.js: -------------------------------------------------------------------------------- 1 | console.log('hello world') -------------------------------------------------------------------------------- /test/raw/src/cwd/all/zoo.js: -------------------------------------------------------------------------------- 1 | console.log('hello world') -------------------------------------------------------------------------------- /test/raw/src/dir/foo.js: -------------------------------------------------------------------------------- 1 | console.log('hello world') -------------------------------------------------------------------------------- /test/raw/src/glob/foo.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); -------------------------------------------------------------------------------- /test/raw/src/plugins/foo.js: -------------------------------------------------------------------------------- 1 | console.log('hello world') -------------------------------------------------------------------------------- /test/raw/src/single/foo.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); -------------------------------------------------------------------------------- /test/raw/src/temp/foo.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); -------------------------------------------------------------------------------- /test/raw/src/deleteFile/foo.js: -------------------------------------------------------------------------------- 1 | console.log('hello world') -------------------------------------------------------------------------------- /test/raw/src/dir/glob/bar.js: -------------------------------------------------------------------------------- 1 | console.log('hello world') -------------------------------------------------------------------------------- /test/raw/src/dir/glob/zoo.js: -------------------------------------------------------------------------------- 1 | console.log('hello world') -------------------------------------------------------------------------------- /test/raw/src/glob/dir/bar.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); -------------------------------------------------------------------------------- /test/raw/src/readDir/bar.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); -------------------------------------------------------------------------------- /test/raw/src/readDir/foo.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); -------------------------------------------------------------------------------- /test/raw/src/rename/base.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); -------------------------------------------------------------------------------- /bone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyicwx/bone/HEAD/bone.png -------------------------------------------------------------------------------- /test/raw/src/deleteFile/concat/bar.js: -------------------------------------------------------------------------------- 1 | console.log('hello world') -------------------------------------------------------------------------------- /test/raw/src/dependencyFile/foo.js: -------------------------------------------------------------------------------- 1 | console.log('hello world') -------------------------------------------------------------------------------- /test/raw/src/overReferences/foo.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); -------------------------------------------------------------------------------- /test/raw/src/duplicateDefinition/foo.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); -------------------------------------------------------------------------------- /test/raw/src/dependencyFile/dependency_a.js: -------------------------------------------------------------------------------- 1 | console.log('dependency_a'); -------------------------------------------------------------------------------- /test/raw/src/dependencyFile/dependency_b.js: -------------------------------------------------------------------------------- 1 | console.log('dependency_b'); -------------------------------------------------------------------------------- /test/raw/src/dependencyFile/dependency_c.js: -------------------------------------------------------------------------------- 1 | console.log('dependency_c'); -------------------------------------------------------------------------------- /docs/cli.md: -------------------------------------------------------------------------------- 1 | bone的命令行支持通过[bone-cli](https://github.com/wyicwx/bone-cli)实现,参考bone-cli的说明文档。 -------------------------------------------------------------------------------- /test/raw/src/less/style.less: -------------------------------------------------------------------------------- 1 | @import "./fn.less"; 2 | 3 | body { 4 | .transition(all ease 400ms); 5 | } -------------------------------------------------------------------------------- /test/bone/callback_error.js: -------------------------------------------------------------------------------- 1 | module.exports = function(buffer, encoding, callback) { 2 | callback(new Error('throw a error in act')); 3 | }; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4.2.4 4 | script: "npm run-script test-travis" 5 | after_script: "npm install coveralls && cat ./coverage/lcov.info | coveralls" -------------------------------------------------------------------------------- /test/bone/dependency.js: -------------------------------------------------------------------------------- 1 | module.exports = function(buffer, encoding, callback) { 2 | this.addDependency('~/src/plugins/dependency_a.js'); 3 | 4 | callback(null, buffer); 5 | }; -------------------------------------------------------------------------------- /lib/data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | virtualFiles: {}, 3 | virtualFileStack: [], 4 | virtualTemporaryFile: [], 5 | virtualFileTraceTree: {}, 6 | virtualFileTraceTreeB: {}, 7 | fileCache: {} 8 | }; -------------------------------------------------------------------------------- /test/bone/dependency_array.js: -------------------------------------------------------------------------------- 1 | module.exports = function(buffer, encoding, callback) { 2 | this.addDependency([ 3 | '~/src/plugins/dependency_a.js', 4 | '~/src/plugins/dependency_b.js' 5 | ]); 6 | 7 | callback(null, buffer); 8 | }; -------------------------------------------------------------------------------- /test/raw/src/less/fn.less: -------------------------------------------------------------------------------- 1 | .pre(@style,@value){ 2 | -webkit-@{style}: @value; 3 | -moz-@{style}: @value; 4 | -ms-@{style}: @value; 5 | @{style}: @value; 6 | } 7 | 8 | .transition(@arg){ 9 | .pre(transition,@arg); 10 | } 11 | .transform(@arg) { 12 | .pre(transform, @arg); 13 | } -------------------------------------------------------------------------------- /test/bone/plugins_author.js: -------------------------------------------------------------------------------- 1 | module.exports = function(buffer, encoding, callback) { 2 | var options = this.options(); 3 | this.cacheable(); 4 | var author = ['/**', ' * @author ' + (options.author || 'anonymous'), ' */', '']; 5 | 6 | author = new Buffer(author.join('\n')); 7 | callback(null, author + buffer); 8 | }; -------------------------------------------------------------------------------- /test/bone/plugins_copyright.js: -------------------------------------------------------------------------------- 1 | module.exports = function(buffer, encoding, callback) { 2 | var options = this.options({ 3 | copyright: 'anonymous' 4 | }); 5 | this.cacheable(); 6 | var copyright = ['/**', ' * @copyright ' + options.copyright, ' */', '']; 7 | 8 | copyright = new Buffer(copyright.join('\n')); 9 | 10 | callback(null, copyright + buffer); 11 | }; -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS = test/*.js 2 | REPORTER = spec 3 | TIMEOUT = 10000 4 | MOCHA_OPTS = 5 | 6 | test: 7 | @NODE_ENV=test mocha \ 8 | --reporter $(REPORTER) \ 9 | --timeout $(TIMEOUT) \ 10 | $(MOCHA_OPTS) \ 11 | $(TESTS) 12 | 13 | test-cov: 14 | @$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER='html-cov > coverage.html' 15 | 16 | test-all: test test-cov 17 | 18 | .PHONY: test-cov test test-all 19 | -------------------------------------------------------------------------------- /log.md: -------------------------------------------------------------------------------- 1 | + 2016-11-12 v0.4.1 2 | + 替换watch组件为gaze 3 | + 增加act函数global参数,global为true情况下,该文件所用的插件读取文件都会带上该处理器进行处理 4 | + 替换FileSystem.getFs的defaultAct参数命为globalAct 5 | + 2016-02-18 v0.3.1 6 | + rename增加extTransport项实现后缀动态映射 7 | + 2016-01-11 v0.3.0 8 | + 增加watch函数,用来开启对项目文件的监听,废弃bone.helper对象 9 | + 2016-01-10 v0.2.1 10 | + dir()函数传递空字符串误判为空bug修复 11 | + 2016-01-10 v0.2.0 12 | + 重构流读取模块、act处理模块,代码结构更加清晰,使用新的加载act方式`bone.require` 13 | + act的runtime通过cacheable函数声明该文件是否可以被缓存 14 | + act的runtime增加addDependency函数来显示声明所依赖的文件 15 | + 2015-12-22 v0.1.4 16 | + 解决windows下watch没有清理cache的问题 17 | + 修改pathResolve针对windows解析根目录的问题 18 | + 2015-12-12 v0.1.2 19 | + FileSystem.getFs增加defaultAct参数,返回的实例在读取文件的接口上默认进行参数指定的处理器处理,增加相应的单元测试 20 | + search增加参数searchAll允许搜索临时文件 21 | + 2015-12-11 release v0.1.0,增加缓存,提升性能,读取文件更快,对前一版本进行大改造,去掉使用频率较少的api,增加一些新的实验性特性 22 | + 2015-06-30 release v0.0.25,基础功能已经完善的版本 23 | + 2014-09-17 终止jt项目,提取核心功能重构bone第一版v0.0.1 24 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # 新手上路 2 | 3 | ###1.通过npm安装bone到你的项目中 4 | ```sh 5 | $ npm install bone --save-dev 6 | ``` 7 | ###2.创建`bonefile.js` 8 | ```shell 9 | touch bonefile.js 10 | ``` 11 | 打开并编辑bonefile.js文件 12 | ```js 13 | var bone = require('bone'); 14 | ``` 15 | ###3.通过`dest()`定义你的虚拟文件夹 16 | ```js 17 | var bone = require('bone'); 18 | 19 | bone.dest('dist') 20 | .src('~/src/**/*'); 21 | ``` 22 | ###4.设置你的虚拟根目录 23 | ```js 24 | var bone = require('bone'); 25 | 26 | bone.dest('dist') 27 | .src('~/src/**/*'); 28 | 29 | bone.setup('./'); 30 | ``` 31 | ###5.调用bone内置命令`bone build`生成文件 32 | ```shell 33 | $ bone build 34 | ``` 35 | **注**:main.js文件在src文件夹下,通过bone映射到了dist/main.js 36 | 37 | bone.fs的API请查看[API](https://github.com/wyicwx/bone/blob/master/docs/api.md)文档获取相关说明 38 | 39 | 关于`bone.dest()`如何[定义一个文件](https://github.com/wyicwx/bone/blob/master/docs/file.md) 40 | 41 | ###其他 42 | 43 | + 命令行工具请参考[bone-cli](https://github.com/wyicwx/bone-cli)模块 44 | + 如何使用并开发自定义[处理器](https://github.com/wyicwx/bone/blob/master/docs/plugin.md) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bone", 3 | "version": "0.4.9", 4 | "description": "web frontend develop toolkit", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "make", 8 | "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" 9 | }, 10 | "author": "wyicwx@gmail.com", 11 | "license": "MIT", 12 | "dependencies": { 13 | "akostream": "^0.0.5", 14 | "chokidar": "^1.7.0", 15 | "colors": "^0.6.1", 16 | "glob": "^7.1.1", 17 | "lodash": "^3.10.1", 18 | "minimatch": "^3.0.2", 19 | "mkdirp": "^0.5.0", 20 | "resolve": "^1.1.6", 21 | "through2": "^0.4.2" 22 | }, 23 | "config": { 24 | "blanket": { 25 | "pattern": "bone/lib" 26 | } 27 | }, 28 | "devDependencies": { 29 | "blanket": "*", 30 | "bone-act-concat": "^0.1.3", 31 | "bone-act-less": "^0.1.1", 32 | "istanbul": "^0.4.5", 33 | "mocha": "*" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/wyicwx/bone.git" 38 | }, 39 | "engines": { 40 | "node": ">=0.12" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 wyicwx<[wyicwx@gmail.com](mailto:wyicwx@gmail.com)> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /lib/kernel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'), 3 | fs = require('fs'), 4 | pkg = require('../package.json'); 5 | 6 | var bone = global.bone = {}; 7 | // bone状态 8 | bone.status = { 9 | base: false, 10 | test: false, 11 | debug: false, 12 | watch: false 13 | }; 14 | // support commander color 15 | require('colors'); 16 | 17 | // version 18 | bone.version = pkg.version; 19 | // setup 20 | bone.setup = function(base) { 21 | if(this.setuped) return; 22 | 23 | this.setuped = true; 24 | 25 | bone.status.base = path.resolve(base); 26 | }; 27 | // 28 | bone.run = function() { 29 | if(!this.setuped) return; 30 | 31 | var FileSystem = require('./fs.js'); 32 | FileSystem.refresh(); 33 | }; 34 | // define dest 35 | bone.dest = function(path) { 36 | var File = require('./file.js'); 37 | var file = new File(); 38 | return file.dest(path); 39 | }; 40 | // utils 41 | bone.utils = require('./utils.js'); 42 | // log 43 | bone.log = require('./log.js'); 44 | // plugin wrapper 45 | bone.wrapper = require('./plugins.js').wrapper; 46 | // load & register act 47 | bone.registerAction = bone.require = require('./plugins.js').registerAction; 48 | // watch 49 | bone.watch = require('./watch.js'); 50 | 51 | module.exports = bone; 52 | 53 | -------------------------------------------------------------------------------- /test/log.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | var log = require('../lib/log.js'); 4 | var bone = require('../index.js'); 5 | var _ = require('lodash'); 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var Data = require('../lib/data.js'); 9 | 10 | bone.status.test = true; 11 | bone.status.watch = true; 12 | describe('bone.log', function() { 13 | it('show log normal', function() { 14 | log('show log normal'); 15 | if (Data.logInfo.pop().indexOf('show log normal') == -1) { 16 | assert.ok(false); 17 | } 18 | }); 19 | 20 | it('show info log normal', function() { 21 | log.info('show info log normal'); 22 | if (Data.logInfo.pop().indexOf('show info log normal') == -1) { 23 | assert.ok(false); 24 | } 25 | }); 26 | 27 | it('show warn log normal', function() { 28 | log.warn('show info warn normal'); 29 | if (Data.logInfo.pop().indexOf('show info warn normal') == -1) { 30 | assert.ok(false); 31 | } 32 | }); 33 | 34 | it('show throw error log in test mode', function() { 35 | assert.throws(function() { 36 | log.error('show info error normal'); 37 | }); 38 | }); 39 | 40 | it('show debug log normal', function() { 41 | bone.status.debug = true; 42 | log.debug('show info debug normal'); 43 | if (Data.logInfo.pop().indexOf('show info debug normal') == -1) { 44 | assert.ok(false); 45 | } 46 | bone.status.debug = false; 47 | }); 48 | }); -------------------------------------------------------------------------------- /docs/plugin.md: -------------------------------------------------------------------------------- 1 | #处理器 2 | 3 | 处理器是针对单一文件内容进行编辑修改的函数,在bone的文件映射过程中通过处理器来对源文件的内容进行修改 4 | 5 | ###调用方式 6 | 存在一个名为processor的处理器,通过`act()`将处理器传入 7 | ```js 8 | var option = {}; 9 | 10 | bone.dest('dist') 11 | .src('~/main.js') 12 | .act(processor(option)); 13 | ``` 14 | **注**:`act()`必须在调用过`src()`后执行,否则会报错 15 | 16 | processor第二个参数传递filter来过滤通过的文件是否使用该处理器 17 | 18 | ```js 19 | bone.dest('dist') 20 | .src('~/main.js') 21 | .act(processor(option, { 22 | filter: function(runtime) { 23 | return true; 24 | } 25 | })) 26 | ``` 27 | 28 | processor也可以不传递参数 29 | 30 | ```js 31 | bone.dest('dist') 32 | .src('~/main.js') 33 | .act(processor); 34 | ``` 35 | 如果你有多个处理,需要调用多次`act()`来传入,处理器将按顺序进行处理 36 | 37 | ```js 38 | bone.dest('dist') 39 | .src('~/main.js') 40 | .act(processorA) 41 | .act(processorB) 42 | .act(processorC); 43 | ``` 44 | 45 | 处理器B获取到的源文件内容是已经被处理器A所处理后的内容,并不是`~/main.js`的源文件内容,处理器C获取到的是处理器B处理之后的内容,依次类推 46 | 47 | ###定义你自己的处理器 48 | ```js 49 | var processor = bone.wrapper(function(buffer, encoding, callback) { 50 | var option = this.option.defaults({ 51 | split: '~', 52 | join: ',' 53 | }); 54 | var content = buffer.toString(encoding); 55 | 56 | content = content.split(option.split) 57 | .join(opiton.join); 58 | 59 | callback(null, content); 60 | }); 61 | ``` 62 | this指向了这个处理器的runtime,通过this可以获得正在处理的源文件的相关信息 63 | ```js 64 | var processor = bone.wrapper(function(buffer, encoding, callback) { 65 | console.log(this); 66 | // return 67 | /* 68 | { 69 | source: 'file full path', 70 | destination: 'file destination path', 71 | option: { 72 | defaults: [Function] 73 | }, 74 | fs: , 75 | bone: , 76 | argvs: { 77 | buffer: , 78 | encoding: 'buffer', 79 | callback: [Function] 80 | } 81 | } 82 | */ 83 | 84 | callback(null, buffer); 85 | }); 86 | ``` 87 | 88 | **option**:传递的参数 89 | 90 | `this.option.defaults()`接受一个默认值对象返回参数 91 | ```js 92 | var scope = { 93 | option: option || {} 94 | }; 95 | scope.option.defaults = function(obj) { 96 | obj || (obj = {}); 97 | return _.extend(obj, option); 98 | }; 99 | 100 | ``` 101 | 102 | **source**:指向处理器正在处理的源文件的路径 103 | 104 | **argvs**:处理函数的参数 -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var Data = require('./data.js'); 3 | var through = require('through2'); 4 | 5 | function print() { 6 | var args = _.toArray(arguments); 7 | var stdout = process.stdout; 8 | 9 | if(bone.status.test) { 10 | if(!print.stream) { 11 | if(!Data.logInfo) { 12 | Data.logInfo = []; 13 | } 14 | print.stream = through(function(buffer, encoding, callback) { 15 | Data.logInfo.push(buffer.toString()); 16 | callback(null, buffer); 17 | }); 18 | } 19 | 20 | stdout = print.stream; 21 | } 22 | 23 | stdout.write(args.join(' ')+'\n'); 24 | } 25 | 26 | 27 | module.exports = function() { 28 | module.exports.log.apply(console, arguments); 29 | }; 30 | 31 | module.exports.info = function(name, msg) { 32 | if(!msg) { 33 | msg = name; 34 | name = null; 35 | } 36 | module.exports.info.count++; 37 | print([name || 'bone', '>>'].join(' ').cyan, msg); 38 | }; 39 | module.exports.info.count = 0; 40 | 41 | module.exports.warn = function(name, msg) { 42 | if(!msg) { 43 | msg = name; 44 | name = null; 45 | } 46 | module.exports.warn.count++; 47 | var status = require('./kernel.js').status; 48 | if(status.watch) { 49 | msg += '\x07'; 50 | } 51 | 52 | print([name || 'bone', '>>'].join(' ').yellow, msg); 53 | }; 54 | module.exports.warn.count = 0; 55 | 56 | module.exports.log = function(name, msg) { 57 | if(!msg) { 58 | msg = name; 59 | name = null; 60 | } 61 | module.exports.log.count++; 62 | print([name || 'bone', '>>'].join(' '), msg); 63 | }; 64 | module.exports.log.count = 0; 65 | 66 | module.exports.error = function(name, msg) { 67 | if(!msg) { 68 | msg = name; 69 | name = null; 70 | } 71 | var status = require('./kernel.js').status; 72 | 73 | var info = [name || 'bone', '>>'].join(' ').red + ' ' + msg; 74 | 75 | throw new Error(info); 76 | }; 77 | 78 | module.exports.debug = function(msg) { 79 | var isDebugMode = require('./kernel.js').status.debug; 80 | if(isDebugMode) { 81 | module.exports.debug.count++; 82 | print('debug >>'.blueBG.white, msg); 83 | } 84 | }; 85 | module.exports.debug.count = 0; -------------------------------------------------------------------------------- /docs/file.md: -------------------------------------------------------------------------------- 1 | #定义一个文件 2 | 3 | 通过`bone.dest()`来指定一个目的文件夹 4 | 5 | **ps**:目的文件夹并不是文件最终路径而是目的文件夹 6 | 7 | ```js 8 | // 定义一个目的文件夹dist 9 | var dist = bone.dest('dist'); 10 | ``` 11 | 通过调用`dist.src()`可以指定源文件 12 | ```js 13 | // 指定单一文件 14 | dist.src('~/src/main.js'); 15 | // 指定glob匹配规则 16 | dist.src('~/src/*.js'); 17 | // 指定多个文件 18 | dist.src([ 19 | '~/src/main.js', 20 | '~/src/lib/jquery.js', 21 | '~/src/page/*.js' 22 | ]); 23 | ``` 24 | 假设我们的目录结构是这样的 25 | ```sh 26 | ┬root 27 | └┬ src 28 | ├── main.js 29 | ├─┬ lib 30 | │ ├── undescore.js 31 | │ ├── jquery.js 32 | │ └── backbone.js 33 | └─┬ page 34 | ├── index.js 35 | └── detail.js 36 | ``` 37 | 通过上面的代码我们定义了一个dist文件夹,于是我们的目录结构变成这样,当然这个dist在你的文件系统里并不存在,dist仅仅是存在配置文件里 38 | ```sh 39 | ┬root 40 | ├┬dist (virtual folder) 41 | │├── main.js 42 | │├── jquery.js 43 | │├── index.js 44 | │└── detail.js 45 | │ 46 | └┬ src 47 | ├── main.js 48 | ├─┬ lib 49 | │ ├── undescore.js 50 | │ ├── jquery.js 51 | │ └── backbone.js 52 | └─┬ page 53 | ├── index.js 54 | └── detail.js 55 | ``` 56 | 通过src指定的文件都被映射到了dist文件夹下,这里可能会有疑问,为什么源文件在不同的目录层级下映射之后却都在dist下? 57 | 58 | 是因为通过src明确指定文件或者glob语法明确匹配文件都只映射文件本身而不映射文件夹,如果需要同时映射文件夹层级的话,一种方法是可以通过再指定目的文件夹来映射 59 | ```js 60 | bone.dest('dist') 61 | .dest('lib') 62 | .src('~/src/lib/jquery.js'); 63 | 64 | dist.dest('dist/page'); 65 | .src('~/src/page/*.js'); 66 | ``` 67 | 或者通过glob匹配文件夹 68 | ```js 69 | dist.src('~/src/main.js'); 70 | dist.src([ 71 | '~/src/lib*/jquery.js', 72 | '~/src/page*/*.js' 73 | ]); 74 | ``` 75 | ###通过`cwd()`改变映射文件夹目录 76 | ```js 77 | dist.cwd('~/src') 78 | .src('lib/jquery.js'); 79 | ``` 80 | 81 | 调用cwd()函数指明源文件的相对文件夹,cwd()调用后再通过src指定的源文件在cwd之下会将cwd目录下的子目录一同映射过去。 82 | 83 | ###重命名 84 | 映射的文件是一一对应的,包括文件名都是相同的,`rename()`提供了重命名的方法 85 | 86 | ```js 87 | dist.src([ 88 | '~/src/main.js', 89 | '~/src/lib/jquery.js', 90 | '~/src/page/*.js' 91 | ]).rename(function(filename) { 92 | return 'dist-'+filename; 93 | }); 94 | ``` 95 | 通过给`rename()`传递函数来批量重命名文件 96 | ```js 97 | dist.src('~/src/main.js') 98 | .rename('dist-main.js'); 99 | ``` 100 | 也可以仅传递一个字符串来重命名 101 | 102 | **ps**:`rename()`必须在调用过`src()`后执行,否则会报错 103 | 104 | ###处理器 105 | 106 | 107 | ```js 108 | var option = {}; 109 | var processor = require('some-act-processor'); 110 | 111 | bone.dest('dist') 112 | .src('~/main.js') 113 | .act(processor(option)); 114 | ``` 115 | 116 | `act()`提供处理器的传入,具体请看如何使用并开发自定义[处理器](https://github.com/wyicwx/bone/blob/master/docs/plugin.md) -------------------------------------------------------------------------------- /lib/cache.js: -------------------------------------------------------------------------------- 1 | var aggre = require('akostream').aggre; 2 | var Stream = require('stream'); 3 | var Data = require('./data.js'); 4 | var debug = bone.log.debug; 5 | 6 | var cache = Data.fileCache; 7 | exports.set = function(filePath, stream) { 8 | if(!bone.status.watch) { 9 | return false; 10 | } 11 | if(arguments.length < 2) { 12 | return false; 13 | } 14 | if(typeof filePath != 'string') { 15 | return false; 16 | } 17 | 18 | debug('set : '+filePath); 19 | if(!cache[filePath]) { 20 | cache[filePath] = { 21 | status: 'init', 22 | buffer: null 23 | }; 24 | } 25 | if(cache[filePath].status === 'set') { 26 | return false; 27 | } 28 | 29 | cache[filePath].status = 'set'; 30 | if(stream instanceof Buffer) { 31 | if(cache[filePath].status === 'set') { 32 | cache[filePath].buffer = stream; 33 | cache[filePath].status = 'cached'; 34 | } 35 | } else if(stream instanceof Stream) { 36 | aggre(stream).on('data', function(buffer) { 37 | if(cache[filePath].status === 'set') { 38 | bone.log.debug('set on data:' + filePath + '.'); 39 | cache[filePath].buffer = buffer; 40 | cache[filePath].status = 'cached'; 41 | } 42 | }).on('end', function() { 43 | if(cache[filePath].status === 'set') { 44 | bone.log.debug('set on end:' + filePath + '.'); 45 | cache[filePath].buffer = new Buffer(0); 46 | cache[filePath].status = 'cached'; 47 | } 48 | }); 49 | } else { 50 | return false; 51 | } 52 | bone.utils.debug.showMem(); 53 | 54 | return true; 55 | }; 56 | 57 | exports.get = function(filePath) { 58 | if(!filePath) { 59 | return false; 60 | } 61 | if(!bone.status.watch) { 62 | return false; 63 | } 64 | if(cache[filePath] && cache[filePath].status === 'cached') { 65 | debug('get : '+filePath); 66 | return cache[filePath].buffer; 67 | } 68 | return false; 69 | }; 70 | 71 | exports.clean = function(filePath) { 72 | if(!bone.status.watch) { 73 | return false; 74 | } 75 | if(cache[filePath]) { 76 | debug('clean : '+filePath); 77 | cache[filePath].buffer = null; 78 | cache[filePath].status = 'clean'; 79 | return true; 80 | } 81 | return false; 82 | }; 83 | 84 | exports.cached = function(filePath) { 85 | if(!bone.status.watch) { 86 | return false; 87 | } 88 | if(cache[filePath] && cache[filePath].status === 'cached') { 89 | debug('cached: '+filePath); 90 | return true; 91 | } 92 | 93 | debug('not cached: '+filePath); 94 | return false; 95 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bone 2 | 3 | 4 | > web前端开发工具 5 | 6 | 7 | 8 | [![NPM version](https://img.shields.io/npm/v/bone.svg?style=flat)](https://npmjs.org/package/bone) [![NPM version](https://img.shields.io/npm/dm/bone.svg?style=flat)](https://npmjs.org/package/bone) [![travis](https://api.travis-ci.org/wyicwx/bone.png)](https://travis-ci.org/wyicwx/bone) 9 | [![Coverage Status](https://coveralls.io/repos/wyicwx/bone/badge.png?branch=master)](https://coveralls.io/r/wyicwx/bone?branch=master) [![Join the chat at https://gitter.im/wyicwx/bone](https://badges.gitter.im/wyicwx/bone.svg)](https://gitter.im/wyicwx/bone?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 10 | 11 | ###核心概念 12 | 13 |   这个模块是Bone的核心功能,为了让使用者更容易读懂Bone的配置文件,核心模块提供了一种类似操作系统里文件系统的概念,我称它为虚拟文件系统,即将一个虚拟的文件地址映射到一个真实存在的文件地址上,同时可以标注虚拟文件是由何种方式对源文件处理(注意这里不对真实文件做任何处理,源文件是指对真实文件的在内容上的拷贝),对源文件的处理模块我称它为[处理器](https://github.com/wyicwx/bone/blob/master/docs/plugin.md),通过下面的示例你可以对Bone的虚拟文件系统有一个初步的了解。 14 | 15 | ###示例 16 | 17 | 这是一个简单Bone配置例子的示范,你需要在项目文件夹下创建`bonefile.js`文件,并安装[bone-cli](https://github.com/wyicwx/bone-cli) 18 | 19 | ```js 20 | var bone = require('bone'); 21 | var connect = require('bone-cli-connect'); 22 | var less = bone.require('bone-act-less'); 23 | var concat = bone.require('bone-act-concat'); 24 | 25 | // 定义生成文件夹dist,用来存放最终的文件 26 | var dist = bone.dest('dist'); 27 | 28 | // 在dist下定义文件夹css(通过dist变量的链式调用为指定定义再其下的文件) 29 | dist.dest('css') 30 | // 指定映射来源src/less下所有.less后缀的文件 31 | .src('~/src/less/*.less') 32 | // 所有文件都经过less处理 33 | .act(less({ 34 | ieCompat: false 35 | })) 36 | // 并重命名为.css后缀 37 | .rename(function(filename) { 38 | return filename.replace(/\.less$/, '.css'); 39 | }); 40 | 41 | // 在dist下定义文件夹html 42 | dist.dest('html') 43 | // 映射src/下将所有.html文件 44 | .src('~/src/*.html'); 45 | 46 | // 在dist下定义文件夹js 47 | dist.dest('js') 48 | // 指定映射来源文件为src/main.js 49 | .src('~/src/main.js') 50 | // 将源文件与src/lib下所有.js后缀的文件合并 51 | .act(concat({ 52 | files: '~/src/lib/*.js' 53 | })); 54 | 55 | // 加载支持connect的插件 56 | bone.cli(connect({ 57 | base: './dist', 58 | port: 8080 59 | })); 60 | ``` 61 | 62 | **ps**:'~/'符号指bonefile.js文件所在的文件夹位置,Bone中的文件路径都使用fs.pathResolve解析,详情请参阅[api](https://github.com/wyicwx/bone/blob/master/docs/api.md#pathresolvefilepath-dir) 63 | 64 | 安装示例中的依赖 65 | ```sh 66 | $ npm install bone bone-cli-connect bone-act-less bone-act-concat --save-dev 67 | ``` 68 | 69 | 启用connect后通过浏览http://localhost:8080查看文件 70 | ```sh 71 | $ bone connect 72 | ``` 73 | 74 | ###Grunt && Glup共存 75 | ```js 76 | bone.task('grunt', { 77 | exec: 'grunt' 78 | }); 79 | ``` 80 | 81 | ```js 82 | bone.task('gulp', { 83 | exec: 'gulp', 84 | params: 'default' 85 | }); 86 | ``` 87 | 88 | bone.task的exec通过开启子进程,调用系统内其他命令行工具 89 | 90 | ###性能 91 | 92 | 当启用`bone-cli-connect`或`bone-cli-proxy`时开启内部缓存,通过bone的fs api接口读取的文件内容都将会以buffer的形式缓存到内存里,当文件内容发生变化时,bone根据文件的依赖清空相应文件的缓存,缓存的粒度控制到单个文件。 93 | 94 | ###文档 95 | 96 | + 快速上手?点击[快速上手](https://github.com/wyicwx/bone/blob/master/docs/getting-started.md) 97 | + 如何[定义虚拟文件](https://github.com/wyicwx/bone/blob/master/docs/file.md) 98 | + bone.fs接口调用文档 [API](https://github.com/wyicwx/bone/blob/master/docs/api.md) 99 | 100 | ###常用服务器 101 | 102 | + [bone-cli-connect](https://github.com/wyicwx/bone-cli-connect) 支持bone的api的静态服务器 103 | + [bone-cli-proxy](https://github.com/wyicwx/bone-cli-proxy) 支持bone的api的代理服务器 104 | 105 | ###常用处理器 106 | 107 | + [bone-act-less](https://github.com/wyicwx/bone-act-less) less编译器 108 | + [bone-act-sass](https://github.com/jansesun/bone-act-sass) sass编译器 109 | + [bone-act-concat](https://github.com/wyicwx/bone-act-concat) 文件合并 110 | + [bone-act-include](https://github.com/wyicwx/bone-act-include) include功能 111 | + [bone-act-uglify](https://github.com/wyicwx/bone-act-uglify) uglify压缩器 112 | + [bone-act-cleancss](https://github.com/wyicwx/bone-act-cleancss) css压缩器 113 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | #fs API列表 2 | 3 | + [pathResolve()](#pathresolvefilepath-dir) 4 | + [createReadStream()](#createreadstreamfilepath) 5 | + [mkdir()](#mkdirdir) 6 | + [rm()](#rmfilepath) 7 | + [createWriteStream()](#createwritestreamfilepath-option) 8 | + [readFile()](#readfilefilepath-option-callback) 9 | + [writeFile()](#writefilefilepath-callback) 10 | + [search()](#searchsearchvalue-option) 11 | + [refresh()](#refresh) 12 | + [readDir()](#readdirdir) 13 | + [existFile()](#existfilefilepath) 14 | 15 | **注意**:`fs`对象只有在`处理器`和`自定义命令行`内通过参数传递获得 16 | 17 | ####pathResolve(filePath, dir) 18 | 解析文件路径为绝对路径 19 | 20 | 在bone中支持`~`,`./`,`../`,`/`四种定位方式都通过`pathResolve`函数来解析。 21 | 参数dir没有定义的情况下为bone.status.base,即`bone.setup(base)`传递的参数base 22 | 23 | ```js 24 | bone.setup('/base/path'); 25 | 26 | fs.pathResolve('~/a/b/c'); 27 | // return 28 | '/base/path/a/b/c' 29 | 30 | fs.pathResolve('./a/b/c'); 31 | // return 32 | '/base/path/a/b/c' 33 | 34 | fs.pathResolve('./a/b/c', '/base/otherpath'); 35 | // return 36 | '/base/otherpath/a/b/c' 37 | 38 | fs.pathResolve('../'); 39 | // return 40 | '/base' 41 | 42 | fs.pathResolve('/base/otherpath'); 43 | // return 44 | '/base/otherpath' 45 | ``` 46 | 47 | ####createReadStream(filePath) 48 | 创建一个读取虚拟文件或者真实文件的流对象,当读取的文件不存在时会丢出一个错误 49 | 50 | 通过option可传递act对象,读取文件后再经过act处理 51 | 52 | ```js 53 | bone.dest('dist') 54 | .src('~/main.js') 55 | 56 | bone.setup('./'); 57 | 58 | var stream = fs.createReadStream('~/dist/main.js'); 59 | 60 | var streamAct = fs.createReadStream('~/dist/main.js', { 61 | act: concat() 62 | }); 63 | ``` 64 | 65 | ####mkdir(dir) 66 | 创建文件夹,支持递归创建 67 | 68 | ```js 69 | fs.mkdir('~/dist/release/js'); 70 | ``` 71 | 72 | ####rm(filePath) 73 | 删除文件或文件夹,当传入参数是一个文件夹时会删除其子文件夹及文件 74 | 75 | *ps:*这个函数非常危险,请确认后再使用 76 | 77 | ```js 78 | fs.rm('~/dist') 79 | ``` 80 | 81 | ####createWriteStream(filePath, option) 82 | 创建一个本地文件的写入流,参数focus强制建立文件夹 83 | 84 | 没有传入focus参数的情况下,在不存在的文件夹路径下创建文件会报错 85 | 86 | ```js 87 | var rStream = fs.createReadStream('') 88 | var wStream = fs.createWriteStream('~/dist/release/js/main.js', { 89 | focus: true 90 | }); 91 | rStream.pipe(wStream); 92 | ``` 93 | 94 | ####readFile(filePath, option, callback) 95 | 读取文件 96 | 97 | 参数同[createReadStream()](#createreadstreamfilepath) 98 | 99 | ```js 100 | fs.readFile('~/dist/not/exist/file.js', function(e, buffer) { 101 | if(e) { 102 | console.log(e); 103 | } 104 | }); 105 | 106 | fs.readFile('~/dist/app.js', { 107 | act: concat() 108 | }, function(e, buffer) { 109 | console.log(buffer.toString()); 110 | }) 111 | ``` 112 | 113 | ####writeFile(filePath, callback) 114 | 写文件 115 | 116 | 参数同[createWriteStream()](#createwritestreamfilepath-option) 117 | 118 | ```js 119 | fs.writeFile('~/dist/foo.js', 'hello world', { 120 | focus: true 121 | }); 122 | 123 | ``` 124 | 125 | ####search(searchValue, option) 126 | 支持[glob](https://github.com/isaacs/node-glob)语法的文件搜索,传入notFs参数仅搜索虚拟文件 127 | 128 | ```js 129 | bone.dest('dist') 130 | .src([ 131 | '~/src/main.js', 132 | '~/src/lib/jquery.js' 133 | ]); 134 | bone.setup('/base/path'); 135 | 136 | bone.search('./**/*'); 137 | // return 138 | '/base/path/dist/main.js' 139 | '/base/path/dist/jquery.js' 140 | '/base/path/src/main.js' 141 | '/base/path/src/lib/jquery.js' 142 | 143 | bone.search('./**/*', { 144 | notFs: true 145 | }); 146 | // return 147 | '/base/path/dist/main.js' 148 | '/base/path/dist/jquery.js' 149 | ``` 150 | ####refresh() 151 | 刷新文件映射关系 152 | 153 | 154 | ####readDir(dir) 155 | 读取文件,传入参数notFs仅读取虚拟文件夹 156 | 157 | ```js 158 | bone.dest('dist') 159 | .src([ 160 | '~/src/main.js', 161 | '~/src/lib/jquery.js' 162 | ]); 163 | bone.setup('/base/path'); 164 | 165 | fs.readDir('./dist'); 166 | // return 167 | ['main.js', 'jquery.js'] 168 | 169 | fs.readDir('~/src', { 170 | notFs: true 171 | }); 172 | // return 173 | [] 174 | ``` 175 | 176 | ####existFile(filePath) 177 | 返回文件是否存在,传入参数notFs仅判断虚拟文件 178 | 179 | ```js 180 | bone.dest('dist') 181 | .src([ 182 | '~/src/main.js', 183 | '~/src/lib/jquery.js' 184 | ]); 185 | bone.setup('/base/path'); 186 | 187 | fs.existFile('./dist/main.js'); 188 | // return 189 | true 190 | 191 | fs.existFile('./dist/index.js'); 192 | // return 193 | false 194 | 195 | fs.existFile('~/src/main.js'); 196 | // return 197 | true 198 | 199 | fs.existFile('~/src/main.js', { 200 | notFs: true 201 | }); 202 | // return 203 | false 204 | ``` -------------------------------------------------------------------------------- /lib/watch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var through = require('through2'), 3 | origin = require('akostream').origin, 4 | path = require('path'), 5 | _ = require('lodash'), 6 | Data = require('./data.js'), 7 | cache = require('./cache.js'), 8 | FileSystem = require('./fs.js'); 9 | 10 | var watcher; 11 | var watchEvent = new (require('events').EventEmitter)(); 12 | var watchPause = false; 13 | 14 | function startWatch() { 15 | var chokidar = require('chokidar'); 16 | var watchedDirectorys = {}; 17 | 18 | _.each(Data.virtualFiles, function(item) { 19 | var dir = path.dirname(item.src); 20 | 21 | watchedDirectorys[dir] = Date.now(); 22 | }); 23 | 24 | bone.status.watch = true; 25 | 26 | if(!watcher) { 27 | watcher = chokidar.watch(_.keys(watchedDirectorys), { 28 | cwd: bone.status.base, 29 | disableGlobbing: true, 30 | ignored: [/node_modules/g] 31 | }); 32 | 33 | watcher.on('all', function(event, file, state) { 34 | if(watchPause) return; 35 | file = FileSystem.fs.pathResolve(file); 36 | let dir = path.dirname(file); 37 | 38 | if (event == 'add') { 39 | if (watchedDirectorys[dir] && (Date.now() - watchedDirectorys[dir] < 1000)) { 40 | return; 41 | } 42 | } 43 | 44 | switch(event) { 45 | case 'change': 46 | event = 'changed'; 47 | watchEvent.emit('changed', file); 48 | break; 49 | case 'unlink': 50 | event = 'deleted'; 51 | watchEvent.emit('deleted', file); 52 | break; 53 | case 'add': 54 | event = 'added'; 55 | watchEvent.emit('added', file); 56 | break; 57 | } 58 | 59 | watchEvent.emit('all', event, file); 60 | }); 61 | 62 | watcher.on('error', function(error) { 63 | throw error; 64 | }); 65 | 66 | FileSystem.event.on('setTraceTree', function(file) { 67 | var realFiles = Data.virtualFileTraceTree[file]; 68 | var Directory = []; 69 | 70 | _.each(realFiles, function(file) { 71 | 72 | var dir = path.dirname(file); 73 | 74 | if (!watchedDirectorys[dir]) { 75 | watchedDirectorys[dir] = Date.now(); 76 | Directory.push(dir); 77 | } 78 | }); 79 | 80 | if (Directory.length) { 81 | watcher.add(Directory); 82 | } 83 | }); 84 | 85 | FileSystem.event.on('readFile', function(file) { 86 | watcher.add(file); 87 | }); 88 | } 89 | } 90 | 91 | var refreshFs = _.debounce(function(event, file) { 92 | bone.log.info(`'${file}' ${event}! refresh file system!`); 93 | FileSystem.refresh(); 94 | }, 1000); 95 | 96 | watchEvent.on('all', function(event, path) { 97 | path = FileSystem.fs.pathResolve(path); 98 | cache.clean(path); 99 | 100 | 101 | switch(event) { 102 | case 'deleted': 103 | _.each(Data.virtualFileTraceTreeB[path], function(host) { 104 | Data.virtualFileTraceTree[host] = _.without(Data.virtualFileTraceTree[host], path); 105 | }); 106 | delete Data.virtualFileTraceTreeB[path]; 107 | case 'added': 108 | // add or delete event clean all virtual file cache 109 | _.each(_.keys(Data.virtualFileTraceTree), function(p) { 110 | cache.clean(p); 111 | }); 112 | 113 | refreshFs(event, path); 114 | bone.log.debug('clean all virtual cache.'); 115 | break; 116 | case 'changed': 117 | _.each(_.clone(Data.virtualFileTraceTreeB[path]), function(p) { 118 | cache.clean(p); 119 | }); 120 | break; 121 | } 122 | }); 123 | 124 | module.exports = function() { 125 | startWatch(); 126 | 127 | return watchEvent; 128 | }; 129 | 130 | module.exports.pause = function() { 131 | watchPause = true; 132 | 133 | return watchEvent; 134 | }; 135 | 136 | module.exports.resume = function() { 137 | watchPause = false; 138 | 139 | return watchEvent; 140 | }; -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var stream = require('akostream'); 3 | var path = require('path'); 4 | var Data = require('./data.js'); 5 | 6 | var utils = {}; 7 | 8 | utils.stream = stream; 9 | 10 | utils.fs = { 11 | // track file stack 12 | track: function(file) { 13 | var FileSystem = require('./fs.js'); 14 | var ReadStream = require('./read_stream.js'); 15 | 16 | if(FileSystem.fs.existFile(file)) { 17 | file = FileSystem.fs.pathResolve(file); 18 | var readStream = new ReadStream(file); 19 | 20 | return _.clone(readStream.trackStack); 21 | } else { 22 | return false; 23 | } 24 | }, 25 | dependentFile: function(file, callback) { 26 | var FileSystem = require('./fs.js'); 27 | 28 | file = FileSystem.fs.pathResolve(file); 29 | if(Data.virtualFileTraceTree[file]) { 30 | callback(null, _.clone(Data.virtualFileTraceTree[file])); 31 | } else { 32 | FileSystem.fs.readFile(file, function(error, buffer) { 33 | if(error) { 34 | callback(error, null); 35 | } else { 36 | callback(null, _.clone(Data.virtualFileTraceTree[file])); 37 | } 38 | }); 39 | } 40 | }, 41 | getByDependentFile: function(file) { 42 | var FileSystem = require('./fs.js'); 43 | 44 | file = FileSystem.fs.pathResolve(file); 45 | return _.clone(Data.virtualFileTraceTreeB[file] || []); 46 | }, 47 | map2local: function(file, callback, option) { 48 | option || (option = {}); 49 | callback || (callback = function() {}); 50 | var FileSystem = require('./fs.js'); 51 | var fs = FileSystem.fs; 52 | var cwd = process.cwd(); 53 | 54 | file = fs.pathResolve(file); 55 | if(fs.existFile(file, {notFs: true})) { 56 | try { 57 | var readStream = fs.createReadStream(file); 58 | var writeStream = fs.createWriteStream(file, {focus: true}); 59 | } catch(e) { 60 | callback(e); 61 | } 62 | 63 | readStream.on('error', function(e) { 64 | callback(e); 65 | }); 66 | readStream.pipe(writeStream, {end: false}); 67 | readStream.on('end', function() { 68 | callback(); 69 | }); 70 | } else { 71 | var errorMsg = 'not exist file '+path.relative(cwd, file); 72 | callback(new Error(errorMsg)); 73 | } 74 | }, 75 | mapAll2local: function(callback, option) { 76 | option || (option = {}); 77 | 78 | var FileSystem = require('./fs.js'); 79 | var fs = FileSystem.fs; 80 | var files = this.getAllVirtualFiles(); 81 | var builded = _.clone(files); 82 | var total = 0; 83 | 84 | var build = function() { 85 | var file = files.shift(); 86 | 87 | if(file) { 88 | total++; 89 | /** 90 | * 受操作系统的限制一次性打开太多文件会报错 91 | * 修改成生成构建好一个再构建下一个 92 | */ 93 | utils.fs.map2local(file, function(e) { 94 | if(e) { 95 | callback(e); 96 | } else { 97 | build(); 98 | } 99 | }, option); 100 | } else { 101 | callback(null, builded); 102 | } 103 | } 104 | build(); 105 | }, 106 | getAllVirtualFiles: function(options) { 107 | !options || (options = {}); 108 | 109 | var files = _.keys(Data.virtualFiles); 110 | var tempFiles = _.clone(Data.virtualTemporaryFile); 111 | 112 | tempFiles.unshift(files); 113 | 114 | files = _.without.apply(_, tempFiles); 115 | 116 | return files; 117 | } 118 | }; 119 | 120 | utils.debug = { 121 | showMem: function() { 122 | if(!bone.status.debug) { 123 | return false; 124 | } 125 | var mem = process.memoryUsage(); 126 | var format = function(bytes) { 127 | return (bytes/1024/1024).toFixed(2)+'MB'; 128 | }; 129 | bone.log.debug('Process: heapTotal '+format(mem.heapTotal) + ' heapUsed ' + format(mem.heapUsed) + ' rss ' + format(mem.rss)); 130 | bone.log.debug('----------------------------------------'); 131 | } 132 | }; 133 | 134 | module.exports = _.extend(utils, _); -------------------------------------------------------------------------------- /test/cache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | var bone = require('../index.js'); 4 | var cache = require('../lib/cache.js'); 5 | 6 | bone.status.test = true; 7 | bone.status.watch = true; 8 | describe('bone.cache', function() { 9 | it('set without parameters or 1 parameter only, return false', function() { 10 | var ret1 = cache.set(); 11 | var ret2 = cache.set('file.js'); 12 | if (ret1 == false && ret2 == false) { 13 | assert.ok(true); 14 | } else { 15 | assert.ok(false); 16 | } 17 | }); 18 | 19 | it('set non-string parameter, return false', function() { 20 | var ret = cache.set({}, {}); 21 | 22 | if (ret == false) { 23 | assert.ok(true); 24 | } else { 25 | assert.ok(false); 26 | } 27 | }); 28 | 29 | it('set second parameter is non-stream, return false', function() { 30 | var ret = cache.set('file.js', {}); 31 | 32 | if (ret == false) { 33 | assert.ok(true); 34 | } else { 35 | assert.ok(false); 36 | } 37 | }); 38 | 39 | it('set buffer to cache', function() { 40 | var buffer = new Buffer('setBufferToCache'); 41 | cache.set('setBufferToCache.js', buffer); 42 | 43 | if (cache.get('setBufferToCache.js').toString() != 'setBufferToCache') { 44 | assert.ok(false); 45 | } 46 | }); 47 | 48 | it('set buffer twice, set buffer after stream, cache stream result', function() { 49 | var origin = bone.utils.stream.origin('setStreamBuffer'); 50 | cache.set('setStreamBuffer.js', origin); 51 | 52 | cache.set('setStreamBuffer.js', new Buffer('setStreamBuffer2')); 53 | 54 | origin.on('end', function() { 55 | var result = cache.get('setStreamBuffer.js').toString(); 56 | 57 | if (result == 'setStreamBuffer') { 58 | assert.ok(true); 59 | } else { 60 | assert.ok(false); 61 | } 62 | }); 63 | }); 64 | 65 | it('set empty stream', function(done) { 66 | var origin = bone.utils.stream.origin(''); 67 | 68 | cache.set('emptyStream.js', origin); 69 | origin.on('end', function() { 70 | process.nextTick(function() { 71 | var result = cache.get('emptyStream.js') 72 | if (!result) { 73 | done(false); 74 | } else { 75 | done(); 76 | } 77 | }); 78 | }); 79 | }); 80 | 81 | it('get cached buffer', function(done) { 82 | var buffer = new Buffer('testCacheBuffer'); 83 | var origin = bone.utils.stream.origin(buffer); 84 | 85 | cache.set('getCachedBuffer.js', origin); 86 | 87 | if (cache.get('getCachedBuffer.js') !== false) { 88 | assert.ok(false); 89 | } 90 | origin.on('end', function() { 91 | var cached = cache.get('getCachedBuffer.js').toString(); 92 | if (cached == 'testCacheBuffer') { 93 | done(); 94 | } else { 95 | done(false); 96 | } 97 | }); 98 | }); 99 | 100 | it('get cache without parameter, return false', function() { 101 | var cached = cache.get(); 102 | 103 | if (cached) { 104 | assert.ok(false); 105 | } 106 | }); 107 | 108 | it('clean cached buffer', function() { 109 | var buffer = new Buffer('cleanCacheBuffer'); 110 | var origin = bone.utils.stream.origin(buffer); 111 | 112 | cache.set('cleanCacheBuffer.js', origin); 113 | 114 | origin.on('end', function() { 115 | var cached = cache.get('cleanCacheBuffer.js').toString(); 116 | if (cached != 'cleanCacheBuffer') { 117 | assert.ok(false); 118 | } 119 | 120 | cache.clean('cleanCacheBuffer.js'); 121 | 122 | cached = cache.get('cleanCacheBuffer.js'); 123 | 124 | if (cached) { 125 | assert.ok(false); 126 | } 127 | }); 128 | }); 129 | 130 | it('clean non-cache file, return false', function() { 131 | var ret = cache.clean('nonCache'); 132 | 133 | if (ret !== false) { 134 | assert.ok(false); 135 | } 136 | }); 137 | 138 | it('cached check', function() { 139 | cache.set('cachedCheck.js', new Buffer('cachedCheck')); 140 | 141 | if (!cache.cached('cachedCheck.js')) { 142 | assert.ok(false); 143 | } 144 | }); 145 | 146 | it('all function return false when watch setting disabled.', function() { 147 | bone.status.watch = false; 148 | var set = cache.set('watchDisabled.js', new Buffer('123123')); 149 | var get = cache.get('watchDisabled.js'); 150 | var clean = cache.clean('watchDisabled.js'); 151 | 152 | if (set || get || clean) { 153 | assert.ok(false); 154 | } 155 | }); 156 | }); -------------------------------------------------------------------------------- /lib/plugins.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var through = require('through2'); 3 | var FileSystem = require('./fs.js'); 4 | var Log = require('./log.js'); 5 | var resolve = require('resolve'); 6 | 7 | function Plugins(processor, options) { 8 | this.processor = processor; 9 | this.name = options.name || null; 10 | this._globalEnable = options.globalEnable; 11 | this._options = options.options; 12 | this._filter = options.filter; 13 | this._formatFilter(); 14 | }; 15 | 16 | Plugins.prototype._formatFilter = function() { 17 | if(_.isObject(this._filter)) { 18 | // format all to array 19 | if(this._filter.ext) { 20 | if(_.isString(this._filter.ext)) { 21 | this._filter.ext = this._filter.ext.split(','); 22 | } else if(!_.isArray(this._filter.ext)) { 23 | this._filter.ext = null; 24 | } 25 | } 26 | 27 | if(this._filter.name) { 28 | if(_.isString(this._filter.name)) { 29 | this._filter.name = this._filter.name.split(','); 30 | } else if(!_.isArray(this._filter.name)) { 31 | this._filter.name = null; 32 | } 33 | } 34 | 35 | if(this._filter.base) { 36 | if(_.isString(this._filter.base)) { 37 | this._filter.base = this._filter.base.split(','); 38 | } else if(!_.isArray(this._filter.base)) { 39 | this._filter.base = null; 40 | } 41 | } 42 | } 43 | }; 44 | 45 | Plugins.prototype.getThroughStream = function() { 46 | var runtime = new Runtime(this._options); 47 | var plugins = this; 48 | 49 | var stream = through.obj(function(data, encoding, callback) { 50 | var fs = data.fs; 51 | 52 | _.extend(runtime, _.pick(data, ['source', 'sourceParsed', 'destination', 'destinationParsed', 'fs'])); 53 | 54 | runtime.addDependency(data.dependency); 55 | // filter processor 56 | if(!plugins.filter(runtime)) { 57 | bone.log.debug('filter false! name: ' + plugins.name + '! source: ' + runtime.source + '!'); 58 | return callback(null, data); 59 | } 60 | 61 | plugins.processor.call(runtime, data.buffer, encoding, function(error, content) { 62 | if(error) { 63 | return callback(error); 64 | } 65 | 66 | var buffer = content; 67 | 68 | if(typeof content == 'string') { 69 | buffer = new Buffer(content); 70 | } 71 | 72 | data.buffer = buffer; 73 | data.dependency = _.uniq(data.dependency.concat(runtime._dependencyList)); 74 | 75 | if(runtime._cacheable == false) { 76 | data.cacheable = false; 77 | } 78 | 79 | callback(null, data); 80 | }); 81 | }); 82 | 83 | return stream; 84 | }; 85 | 86 | Plugins.prototype.filter = function(runtime) { 87 | if(this._filter) { 88 | var runtimeInfo = _.pick(runtime, 'source', 'sourceParsed', 'destination', 'destinationParsed'); 89 | 90 | if(_.isFunction(this._filter)) { 91 | return this._filter.call(null, runtimeInfo); 92 | } else if(_.isObject(this._filter)){ 93 | if(this._filter.ext) { 94 | if(_.indexOf(this._filter.ext, runtimeInfo.sourceParsed.ext) != -1) { 95 | return true; 96 | } else { 97 | return false; 98 | } 99 | } 100 | if(this._filter.name) { 101 | if(_.indexOf(this._filter.name, runtimeInfo.sourceParsed.name) != -1) { 102 | return true; 103 | } else { 104 | return false; 105 | } 106 | } 107 | if(this._filter.base) { 108 | if(_.indexOf(this._filter.base, runtimeInfo.sourceParsed.base) != -1) { 109 | return true; 110 | } else { 111 | return false; 112 | } 113 | } 114 | } 115 | } 116 | return true; 117 | }; 118 | 119 | Plugins.prototype.globalEnable = function() { 120 | return !(this._globalEnable === false); 121 | }; 122 | 123 | function Runtime(options) { 124 | this._options = options || {}; 125 | this._cacheable = false; 126 | this._dependencyList = []; 127 | this.bone = bone; 128 | }; 129 | 130 | Runtime.prototype.options = function(options) { 131 | return _.extend({}, options, this._options); 132 | }; 133 | 134 | Runtime.prototype.cacheable = function() { 135 | this._cacheable = true; 136 | }; 137 | 138 | Runtime.prototype.addDependency = function(dependencyFile) { 139 | var dependencyList = this._dependencyList; 140 | if(_.isArray(dependencyFile)) { 141 | _.each(dependencyFile, function(file) { 142 | dependencyList.push(FileSystem.fs.pathResolve(file)); 143 | }); 144 | } else if(_.isString(dependencyFile)) { 145 | dependencyList.push(FileSystem.fs.pathResolve(dependencyFile)); 146 | } 147 | }; 148 | 149 | Runtime.prototype.getDependency = function() { 150 | return _.clone(this._dependencyList); 151 | }; 152 | 153 | module.exports.Plugins = Plugins; 154 | 155 | module.exports.registerAction = function(moduleName) { 156 | var log = require('./log.js'); 157 | var act; 158 | 159 | try { 160 | var mod = resolve.sync(moduleName, {basedir: bone.status.base}); 161 | } catch(e) {} 162 | if(mod) { 163 | mod = require(mod); 164 | } else { 165 | try { 166 | var mod = require(moduleName); 167 | } catch(e) { 168 | throw e; 169 | } 170 | } 171 | 172 | mod.name = moduleName; 173 | 174 | if(_.isFunction(mod)) { 175 | act = mod; 176 | } else { 177 | if(!mod.act) { 178 | log.error('require', 'module "'+moduleName+'" is no design for bone!'); 179 | } else { 180 | act = mod.act; 181 | } 182 | } 183 | 184 | return this.wrapper(act, mod); 185 | }; 186 | 187 | module.exports.wrapper = function(act, mod) { 188 | mod || (mod = {}); 189 | return function(options, info) { 190 | options || (options = {}); 191 | info || (info = {}); 192 | 193 | var plugins = new Plugins(act, { 194 | options: options, 195 | filter: info.filter || mod.filter, 196 | name: mod.name, 197 | globalEnable: mod.globalEnable 198 | }); 199 | 200 | return plugins; 201 | }; 202 | }; -------------------------------------------------------------------------------- /lib/read_stream.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var FileSystem = require('./fs.js'); 5 | var bonefs = FileSystem.fs; 6 | var aggre = require('akostream').aggre; 7 | var cache = require('./cache.js'); 8 | var Data = require('./data.js'); 9 | var through = require('through2'); 10 | var Plugins = require('./plugins.js'); 11 | 12 | function ReadStream(file, option, acts) { 13 | option || (option = {}); 14 | option.quote || (option.quote = {}); 15 | // 对真实文件的依赖 16 | this.dependentFile = null; 17 | // 文件完整路径 18 | this.path = file; 19 | // 文件夹 20 | this.dir = path.dirname(file); 21 | // 文件名 22 | this.basename = path.basename(file); 23 | // 是否虚拟文件 24 | this.isVirtual = bonefs.existFile(file, { 25 | notFs: true 26 | }); 27 | // 来源文件 28 | this.source = null; 29 | // 依赖的真实文件, resolveDependentFile之后可以获取 30 | this.originSource = null; 31 | // 文件源流 32 | this.sourceStream = null; 33 | // 读取流参数 34 | this.option = option; 35 | // 依赖文件堆栈 36 | this.trackStack = []; 37 | // 附加act 38 | this.acts = !acts ? [] : (_.isArray(acts) ? acts : [acts]); 39 | // 附加act依赖的文件 40 | this.actsDependent = []; 41 | this.onEnd = option.onEnd; 42 | // 源文件流 43 | this._sourceStream = null; 44 | // 解析文件链 45 | this._checkOverReferences(); 46 | this._resolve(); 47 | // _resolveDependentFile(this); 48 | } 49 | 50 | ReadStream.prototype._checkOverReferences = function() { 51 | var option = this.option; 52 | 53 | if (this.path in option.quote) { 54 | if (this.isVirtual) { 55 | bone.log.error('File over references: ' + this.path + '!'); 56 | } 57 | } else { 58 | option.quote[this.path] = true; 59 | } 60 | }; 61 | 62 | ReadStream.prototype._resolve = function() { 63 | this.trackStack.push(this.path); 64 | 65 | if (!this.isVirtual) { 66 | this.source = this.path; 67 | this.originSource = this.path; 68 | } else { 69 | this.source = Data.virtualFiles[this.path].src; 70 | this._sourceStream = new ReadStream(Data.virtualFiles[this.path].src, this.option); 71 | this.originSource = this._sourceStream.originSource; 72 | this.trackStack = this.trackStack.concat(this._sourceStream.trackStack); 73 | } 74 | }; 75 | 76 | ReadStream.prototype._read = function() { 77 | var originStream; 78 | var readStream = this; 79 | if (cache.cached(this.path)) { 80 | originStream = bone.utils.stream.origin(cache.get(this.path)); 81 | } else { 82 | if (this._sourceStream) { 83 | originStream = this._sourceStream.getStream(); 84 | } else { 85 | originStream = bone.utils.stream.origin(fs.readFileSync(this.path)); 86 | } 87 | } 88 | 89 | // transform to obj stream. 90 | var objStream = through.obj(function(buffer, encoding, callback) { 91 | var data = { 92 | buffer: buffer, 93 | source: readStream.originSource, 94 | sourceParsed: path.parse(readStream.originSource), 95 | destination: readStream.path, 96 | destinationParsed: path.parse(readStream.path), 97 | cacheable: true, 98 | dependency: [readStream.source], 99 | fs: FileSystem.getFs({ 100 | captureFile: true, 101 | globalAct: readStream.isVirtual ? Data.virtualFiles[readStream.path].globalActs : [] 102 | }) 103 | }; 104 | callback(null, data); 105 | }); 106 | 107 | this._readStream = originStream.pipe(objStream); 108 | if (!cache.cached(this.path)) { 109 | this._handlerAct(); 110 | } 111 | }; 112 | 113 | ReadStream.prototype._processAct = function(act, gloabl) { 114 | var readStream = this; 115 | // fix issue https://github.com/wyicwx/bone/issues/2 116 | if (!(act instanceof Plugins.Plugins)) { 117 | act = act(); 118 | } 119 | 120 | if (!(act instanceof Plugins.Plugins)) { 121 | return; 122 | } 123 | 124 | if(gloabl && !act.globalEnable()) { 125 | bone.log.warn('The plugin "' + act.name + '" fobidden to use in gloabl!'); 126 | return; 127 | } 128 | 129 | var targetStream = act.getThroughStream(); 130 | targetStream.on('error', function(error) { 131 | readStream._readStream.emit('error', error); 132 | }); 133 | this._readStream = this._readStream.pipe(targetStream); 134 | }; 135 | 136 | ReadStream.prototype._handlerAct = function() { 137 | var readStream = this; 138 | 139 | if (this.isVirtual) { 140 | var acts = Data.virtualFiles[this.path].acts; 141 | _.each(acts, function(act) { 142 | this._processAct(act); 143 | }, this); 144 | } 145 | 146 | var collectStream = through.obj(function(data, encoding, callback) { 147 | if (data.cacheable) { 148 | cache.set(readStream.path, data.buffer); 149 | } 150 | if (readStream.isVirtual) { 151 | var dependency = data.dependency.concat(data.fs._readFiles); 152 | dependency = _.uniq(dependency); 153 | FileSystem.setTraceTree(readStream.path, dependency); 154 | // 清空插件所依赖的文件,防止和全局插件依赖文件混在一起 155 | data.fs._readFiles.length = 0; 156 | } else { 157 | FileSystem.event.emit('readFile', readStream.path); 158 | } 159 | 160 | callback(null, data); 161 | }); 162 | 163 | this._readStream = this._readStream.pipe(collectStream); 164 | }; 165 | 166 | ReadStream.prototype._handlerGlobalAct = function() { 167 | var readStream = this; 168 | 169 | _.each(this.acts, function(act) { 170 | this._processAct(act, true); 171 | }, this); 172 | 173 | var collectStream = through.obj(function(data, encoding, callback) { 174 | readStream.actsDependent = readStream.actsDependent.concat(data.fs._readFiles); 175 | callback(null, data); 176 | }); 177 | 178 | this._readStream = this._readStream.pipe(collectStream); 179 | }; 180 | 181 | ReadStream.prototype._end = function() { 182 | var readStream = this; 183 | this._handlerGlobalAct(); 184 | 185 | var endStream = through.obj(function(data, encoding, callback) { 186 | if(readStream.onEnd) { 187 | readStream.onEnd(); 188 | } 189 | callback(null, data.buffer); 190 | }); 191 | this._readStream = this._readStream.pipe(endStream); 192 | }; 193 | 194 | ReadStream.prototype.getStream = function() { 195 | this._read(); 196 | this._end(); 197 | 198 | return this._readStream; 199 | }; 200 | 201 | module.exports = ReadStream; -------------------------------------------------------------------------------- /test/plugins.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | var log = require('../lib/log.js'); 4 | var bone = require('../index.js'); 5 | var _ = require('lodash'); 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var Plugins = require('../lib/plugins.js').Plugins; 9 | var FileSystem = require('../lib/fs.js'); 10 | var bonefs; 11 | 12 | bone.setup('./test/raw'); 13 | bonefs = FileSystem.fs; 14 | 15 | function act(buffer, encoding, callback) { 16 | callback(null, buffer); 17 | } 18 | 19 | var runtimeJsFile = { 20 | source: '/tmp/runtime.js', 21 | sourceParsed: path.parse('/tmp/runtime.js'), 22 | destination: '/tmp/dest/runtime.js', 23 | destinationParsed: path.parse('/tmp/dest/runtime.js') 24 | }; 25 | var runtimeExtJsFile = { 26 | source: '/tmp/runtime_ext.js', 27 | sourceParsed: path.parse('/tmp/runtime_ext.js'), 28 | destination: '/tmp/dest/runtime_ext.js', 29 | destinationParsed: path.parse('/tmp/dest/runtime_ext.js') 30 | }; 31 | var runtimeCssFile = { 32 | source: '/tmp/runtime.css', 33 | sourceParsed: path.parse('/tmp/runtime.css'), 34 | destination: '/tmp/dest/runtime.css', 35 | destinationParsed: path.parse('/tmp/dest/runtime.css') 36 | }; 37 | var runtimeHtmlFile = { 38 | source: '/tmp/runtime.html', 39 | sourceParsed: path.parse('/tmp/runtime.html'), 40 | destination: '/tmp/dest/runtime.html', 41 | destinationParsed: path.parse('/tmp/dest/runtime.html') 42 | }; 43 | 44 | bone.status.test = true; 45 | bone.status.watch = true; 46 | 47 | describe('plugins', function() { 48 | it('require module what plugins not design for bone will throw error', function() { 49 | assert.throws(function() { 50 | bone.require('colors'); 51 | }); 52 | }); 53 | 54 | it('require module what not exits will throw error', function() { 55 | assert.throws(function() { 56 | bone.require('fantasy_module'); 57 | }); 58 | }); 59 | 60 | it('function filter', function() { 61 | var pluginsAlwaysTrue = new Plugins(act, { 62 | filter: function() { 63 | return true; 64 | } 65 | }); 66 | 67 | var pluginsAlwaysFalse = new Plugins(act, { 68 | filter: function() { 69 | return false; 70 | } 71 | }); 72 | 73 | if(!pluginsAlwaysTrue.filter(runtimeJsFile)) { 74 | assert.ok(false); 75 | } 76 | 77 | if(pluginsAlwaysFalse.filter(runtimeJsFile)) { 78 | assert.ok(false); 79 | } 80 | }); 81 | 82 | it('ext filter', function() { 83 | var pluginsString = new Plugins(act, { 84 | filter: { 85 | ext: '.js,.css' 86 | } 87 | }); 88 | 89 | var pluginsArray = new Plugins(act, { 90 | filter: { 91 | ext: ['.js', '.css'] 92 | } 93 | }); 94 | 95 | var pluginsObject = new Plugins(act, { 96 | filter: { 97 | ext: {} 98 | } 99 | }); 100 | 101 | if(!pluginsString.filter(runtimeJsFile) || !pluginsString.filter(runtimeCssFile)) { 102 | assert.ok(false); 103 | } 104 | 105 | if(!pluginsArray.filter(runtimeJsFile) || !pluginsString.filter(runtimeCssFile)) { 106 | assert.ok(false); 107 | } 108 | 109 | if(!pluginsObject.filter(runtimeJsFile) || !pluginsObject.filter(runtimeCssFile)) { 110 | assert.ok(false); 111 | } 112 | 113 | if(!pluginsObject.filter(runtimeJsFile)) { 114 | assert.ok(false); 115 | } 116 | 117 | if(pluginsString.filter(runtimeHtmlFile) || pluginsArray.filter(runtimeHtmlFile)) { 118 | assert.ok(false); 119 | } 120 | }); 121 | 122 | it('name filter', function() { 123 | var pluginsString = new Plugins(act, { 124 | filter: { 125 | name: 'runtime,runtime_ext' 126 | } 127 | }); 128 | 129 | var pluginsArray = new Plugins(act, { 130 | filter: { 131 | name: ['runtime', 'runtime_ext'] 132 | } 133 | }); 134 | 135 | var pluginsObject = new Plugins(act, { 136 | filter: { 137 | name: {} 138 | } 139 | }); 140 | 141 | if(!pluginsString.filter(runtimeJsFile) || !pluginsString.filter(runtimeExtJsFile)) { 142 | assert.ok(false); 143 | } 144 | 145 | if(!pluginsArray.filter(runtimeJsFile) || !pluginsString.filter(runtimeExtJsFile)) { 146 | assert.ok(false); 147 | } 148 | 149 | if(!pluginsObject.filter(runtimeJsFile) || !pluginsObject.filter(runtimeExtJsFile)) { 150 | assert.ok(false); 151 | } 152 | }); 153 | 154 | it('base filter', function() { 155 | var pluginsString = new Plugins(act, { 156 | filter: { 157 | base: 'runtime.js,runtime.css' 158 | } 159 | }); 160 | 161 | var pluginsArray = new Plugins(act, { 162 | filter: { 163 | base: ['runtime.js', 'runtime.css'] 164 | } 165 | }); 166 | 167 | var pluginsObject = new Plugins(act, { 168 | filter: { 169 | base: {} 170 | } 171 | }); 172 | 173 | if(!pluginsString.filter(runtimeJsFile) || !pluginsString.filter(runtimeCssFile)) { 174 | assert.ok(false); 175 | } 176 | 177 | if(!pluginsArray.filter(runtimeJsFile) || !pluginsString.filter(runtimeCssFile)) { 178 | assert.ok(false); 179 | } 180 | 181 | if(!pluginsObject.filter(runtimeJsFile) || !pluginsObject.filter(runtimeCssFile)) { 182 | assert.ok(false); 183 | } 184 | 185 | if(pluginsArray.filter(runtimeHtmlFile)) { 186 | assert.ok(false); 187 | } 188 | }); 189 | 190 | it('options() set default value for some key', function(done) { 191 | bonefs.readFile('~/dist/plugins/copyright.js', function(err, buffer) { 192 | var content = buffer.toString(); 193 | 194 | if (~content.search('@copyright anonymous')) { 195 | done(); 196 | } else { 197 | done(false); 198 | } 199 | }); 200 | }); 201 | 202 | it('addDependency() add dependency array to dependecy list', function(done) { 203 | bone.utils.fs.dependentFile('~/dist/plugins/addDependencyArray.js', function(error, dependencies) { 204 | if(error) { 205 | return done(false); 206 | } 207 | 208 | var dependencyFile = [ 209 | bonefs.pathResolve('~/src/plugins/foo.js'), 210 | bonefs.pathResolve('~/src/plugins/dependency_a.js'), 211 | bonefs.pathResolve('~/src/plugins/dependency_b.js') 212 | ]; 213 | 214 | if(_.intersection(dependencies, dependencyFile).length == dependencyFile.length) { 215 | done(); 216 | } else { 217 | done(false); 218 | } 219 | }); 220 | }); 221 | 222 | it('addDependency() add single to dependecy list', function(done) { 223 | bone.utils.fs.dependentFile('~/dist/plugins/addDependency.js', function(error, dependencies) { 224 | if(error) { 225 | return done(false); 226 | } 227 | 228 | var dependencyFile = [ 229 | bonefs.pathResolve('~/src/plugins/foo.js'), 230 | bonefs.pathResolve('~/src/plugins/dependency_a.js') 231 | ]; 232 | 233 | if(_.intersection(dependencies, dependencyFile).length == dependencyFile.length) { 234 | done(); 235 | } else { 236 | done(false); 237 | } 238 | }); 239 | }); 240 | 241 | it('filter is runing correct', function(done) { 242 | bonefs.readFile('~/dist/less/style.css', function(error, buffer) { 243 | if(error) { 244 | return done(false); 245 | } 246 | 247 | var content = buffer.toString(); 248 | 249 | if(content.indexOf('@import "./fn.less";') != -1) { 250 | done(false); 251 | } else { 252 | done(); 253 | } 254 | }); 255 | }); 256 | 257 | it('filter filter some not in rule', function(done) { 258 | bonefs.readFile('~/dist/less/notCompile.js', function(error, buffer) { 259 | if(error) { 260 | return done(false); 261 | } 262 | 263 | var content = buffer.toString(); 264 | 265 | if(content.indexOf('@import "./fn.less";') != -1) { 266 | done(); 267 | } else { 268 | done(false); 269 | } 270 | }); 271 | }); 272 | 273 | it('callback error ', function(done) { 274 | bonefs.readFile('dist/plugins/error.js', function(error, buffer) { 275 | if(error) { 276 | done(); 277 | } else { 278 | done(false); 279 | } 280 | }); 281 | }); 282 | }); -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | var cache = require('../lib/cache.js'); 4 | var bone = require('../index.js'); 5 | var _ = require('lodash'); 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var Data = require('../lib/data.js'); 9 | 10 | 11 | bone.status.test = true; 12 | bone.status.watch = true; 13 | describe('bone.utils', function() { 14 | var bonefs; 15 | var FileSystem; 16 | 17 | before(function() { 18 | FileSystem = require('../lib/fs.js'); 19 | bonefs = FileSystem.getFs(); 20 | }); 21 | 22 | it('track a virtual file', function() { 23 | var trackFile = bone.utils.fs.track('dist/track/foo.js'); 24 | 25 | if (trackFile.length == 3) { 26 | if (trackFile[0] == bonefs.pathResolve('dist/track/foo.js')) { 27 | if (trackFile[1] == bonefs.pathResolve('dist/single/foo.js')) { 28 | if (trackFile[2] == bonefs.pathResolve('src/single/foo.js')) { 29 | return assert.ok(true); 30 | } 31 | } 32 | } 33 | } 34 | 35 | assert.ok(false); 36 | }); 37 | 38 | it('track a rename virtual file', function() { 39 | var trackFile = bone.utils.fs.track('dist/track/bar.js'); 40 | 41 | if (trackFile.length == 4) { 42 | if (trackFile[0] == bonefs.pathResolve('dist/track/bar.js')) { 43 | if (trackFile[1] == bonefs.pathResolve('dist/single/zoo.js')) { 44 | if (trackFile[2] == bonefs.pathResolve('dist/single/foo.js')) { 45 | if (trackFile[3] == bonefs.pathResolve('src/single/foo.js')) { 46 | return assert.ok(true); 47 | } 48 | } 49 | } 50 | } 51 | } 52 | assert.ok(false); 53 | }); 54 | 55 | it('track a not exist file will return false', function() { 56 | if (bone.utils.fs.track('~/dist/not/exist/file.js') === false) { 57 | assert.ok(true); 58 | } else { 59 | assert.ok(false); 60 | } 61 | }); 62 | 63 | it('dependentFile', function(done) { 64 | bone.utils.fs.dependentFile('dist/dependencyFile/dependency.js', function(error, dependencies) { 65 | var dependencyFile = [ 66 | bonefs.pathResolve('~/src/dependencyFile/foo.js'), 67 | bonefs.pathResolve('~/src/dependencyFile/dependency_a.js'), 68 | bonefs.pathResolve('~/src/dependencyFile/dependency_b.js') 69 | ]; 70 | 71 | if(_.intersection(dependencies, dependencyFile).length == dependencyFile.length) { 72 | done(); 73 | } else { 74 | done(false); 75 | } 76 | }); 77 | }); 78 | 79 | it('dependentFile a not exist file will return false', function(done) { 80 | bone.utils.fs.dependentFile('~/dist/not/exist/file.js', function(err) { 81 | if (err) { 82 | done(); 83 | } else { 84 | done(false); 85 | } 86 | }); 87 | }); 88 | 89 | it('dependentFile multi times at same time', function(done) { 90 | var map = {}; 91 | var end = function() { 92 | if (map.a && map.b) { 93 | if (map.a.length == 3 && map.b.length == 4) { 94 | done(); 95 | } else { 96 | done(false); 97 | } 98 | } 99 | }; 100 | 101 | bone.utils.fs.dependentFile('dist/dependencyFile/dependency.js', function(err, dependencies) { 102 | map.a = dependencies; 103 | end(); 104 | }); 105 | 106 | bone.utils.fs.dependentFile('dist/dependencyFile/dependency_2.js', function(err, dependencies) { 107 | map.b = dependencies; 108 | end(); 109 | }); 110 | }); 111 | 112 | it('dependentFile virtual file what source is virtual file', function(done) { 113 | bone.utils.fs.dependentFile('dist/dependencyFile/dependency_3.js', function(error, dependencies) { 114 | var dependencyFile = [ 115 | bonefs.pathResolve('~/src/dependencyFile/foo.js'), 116 | bonefs.pathResolve('~/src/dependencyFile/dependency_a.js'), 117 | bonefs.pathResolve('~/src/dependencyFile/dependency_b.js'), 118 | bonefs.pathResolve('~/src/dependencyFile/dependency_c.js') 119 | ]; 120 | 121 | if(_.intersection(dependencies, dependencyFile).length == dependencyFile.length) { 122 | done(); 123 | } else { 124 | done(false); 125 | } 126 | }); 127 | }); 128 | 129 | it('map2local virtual file', function(done) { 130 | var dirpath = bonefs.pathResolve('~/dist/single'); 131 | var filepath = bonefs.pathResolve('~/dist/single/foo.js'); 132 | 133 | bonefs.rm(dirpath); 134 | 135 | bone.utils.fs.map2local(filepath, function(error) { 136 | if (fs.existsSync(filepath)) { 137 | bonefs.rm(dirpath); 138 | done(); 139 | } else { 140 | bonefs.rm(dirpath); 141 | done(false); 142 | } 143 | }); 144 | }); 145 | 146 | it('map2local real file', function(done) { 147 | bone.utils.fs.map2local('dist/single/foo.js', function(error) { 148 | if (error) { 149 | return done(false); 150 | } 151 | 152 | done(); 153 | }); 154 | }); 155 | 156 | it('map2local not exist file', function(done) { 157 | bone.utils.fs.map2local('dist/notExist/foo.js', function(error) { 158 | if(error) { 159 | return done(); 160 | } 161 | done(false); 162 | }); 163 | }); 164 | 165 | it('mapAll2local in act throw error', function(done) { 166 | bone.utils.fs.mapAll2local(function(error) { 167 | var File = require('../lib/file.js'); 168 | 169 | _.each(File.fileList, function(item) { 170 | if (item.destination == 'plugins' && item.renameFn == 'error.js') { 171 | File.fileList = _.without(File.fileList, item); 172 | } 173 | }); 174 | bonefs.refresh(); 175 | if(error) { 176 | done(); 177 | } else { 178 | done(false); 179 | } 180 | }); 181 | }); 182 | 183 | it('mapAll2local', function(done) { 184 | var rmTmp = function() { 185 | bonefs.rm(path.join(__dirname, './raw/dist')); 186 | bonefs.rm(path.join(__dirname, './raw/cwd')); 187 | bonefs.rm(path.join(__dirname, './raw/dev')); 188 | bonefs.rm(path.join(__dirname, './raw/search')); 189 | }; 190 | 191 | bone.watch.pause(); 192 | 193 | rmTmp(); 194 | bonefs.refresh(); 195 | 196 | bone.utils.fs.mapAll2local(function(error, list) { 197 | if (error) { 198 | console.log(error); 199 | return done(false); 200 | } 201 | 202 | if (!list.length) { 203 | return done(false); 204 | } 205 | _.each(list, function(file) { 206 | if (!fs.existsSync(file)) { 207 | assert.ok(false); 208 | } 209 | }); 210 | 211 | rmTmp(); 212 | bone.watch.resume(); 213 | done(); 214 | }); 215 | }); 216 | 217 | it('getAllVirtualFiles', function() { 218 | var FileSystem = require('../lib/fs.js'); 219 | var filesF = _.keys(FileSystem.files); 220 | var files = bone.utils.fs.getAllVirtualFiles(); 221 | 222 | if (files.length && ArrayContain(filesF, files)) { 223 | assert.ok(true); 224 | } else { 225 | assert.ok(false); 226 | } 227 | }); 228 | 229 | it('getByDependent file', function() { 230 | var filePath = bonefs.pathResolve('~/src/single/foo.js'); 231 | var files = bone.utils.fs.getByDependentFile(filePath); 232 | 233 | if (files.length <= 0) { 234 | assert.ok(false); 235 | } 236 | var intersection = _.intersection(files, [filePath]); 237 | if (intersection.length) { 238 | assert.ok(false); 239 | } 240 | }); 241 | }); 242 | 243 | describe('bone.debug', function() { 244 | it('showMem', function() { 245 | bone.status.debug = true; 246 | assert.doesNotThrow(function() { 247 | bone.utils.debug.showMem(); 248 | bone.status.debug = false; 249 | }); 250 | }); 251 | }); 252 | 253 | function ArrayContain(a, b, option) { 254 | option || (option = {}); 255 | var illagel = true; 256 | if (option.strict) { 257 | if (a.length !== b.length) { 258 | illagel = false; 259 | } 260 | } 261 | _.each(a, function(file) { 262 | if (!~_.indexOf(b, file)) { 263 | illagel = false; 264 | } 265 | }); 266 | if (illagel) { 267 | return true; 268 | } else { 269 | return false; 270 | } 271 | } -------------------------------------------------------------------------------- /lib/file.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var _ = require('lodash'); 5 | var minimatch = require('minimatch'); 6 | var Log = require('./log.js'); 7 | var FileSystem = require('./fs.js'); 8 | 9 | function File(destination, options) { 10 | options || (options = {}); 11 | this.parentPath = options.parent || ''; // raw path 12 | this.destination = destination || ''; 13 | this.source = null; 14 | this.renameFn = null; 15 | this.dirFn = null; 16 | this.cwdPath = options.cwd || ''; 17 | this.acts = []; 18 | this.globalActs = []; 19 | this.isTemporary = options.isTemporary || false; 20 | } 21 | 22 | File.prototype.dest = function(p) { 23 | if(!_.isString(p)) { 24 | throw new Error('dest() only support string!') 25 | } 26 | 27 | return new File(p, { 28 | parent: path.join(this.parentPath, this.destination), 29 | isTemporary: this.isTemporary 30 | }); 31 | }; 32 | 33 | File.prototype.cwd = function(cwd) { 34 | if(this.source) { 35 | throw new Error('call cwd() before src()!'); 36 | } 37 | 38 | if(!_.isString(cwd)) { 39 | throw new Error('dest() only support string!') 40 | } 41 | 42 | var file = new File(this.destination, { 43 | parent: this.parentPath, 44 | cwd: cwd, 45 | isTemporary: this.isTemporary 46 | }); 47 | 48 | return file; 49 | }; 50 | 51 | File.prototype.src = function(src) { 52 | if(_.isArray(src)) { 53 | src.map(function(src) { 54 | if(!_.isString(src)) { 55 | throw new Error('src() only support string and string array!'); 56 | } 57 | }); 58 | } else if(!_.isString(src)) { 59 | throw new Error('src() only support string and string array!'); 60 | } 61 | 62 | var file = new File(this.destination, { 63 | parent: this.parentPath, 64 | cwd: this.cwdPath, 65 | isTemporary: this.isTemporary 66 | }); 67 | file.source = src; 68 | File.fileList.push(file); 69 | 70 | return file; 71 | }; 72 | 73 | File.prototype.dir = function(dir) { 74 | if(!this.source) { 75 | throw new Error('call src() first!'); 76 | } 77 | 78 | if(!_.isFunction(dir) && !_.isString(dir)) { 79 | throw new Error('dir() only support string and function!'); 80 | } 81 | 82 | this.dirFn = dir; 83 | 84 | return this; 85 | }; 86 | 87 | File.prototype.rename = function(fn) { 88 | if(!this.source) { 89 | throw new Error('call src() first!'); 90 | } 91 | if(!fn) return; 92 | 93 | if(!_.isFunction(fn) && !_.isString(fn) && !_.isObject(fn)) { 94 | throw new Error('rename() only support string, function and object!'); 95 | } 96 | 97 | this.renameFn = fn; 98 | 99 | return this; 100 | }; 101 | 102 | File.prototype.act = function(fn, options) { 103 | options || (options = {}); 104 | if(!this.source) { 105 | throw new Error('call src() before act()!'); 106 | } 107 | this.acts.push(fn); 108 | 109 | if(options.global) { 110 | this.globalActs.push(fn); 111 | } 112 | 113 | return this; 114 | }; 115 | 116 | File.prototype.destroy = function() { 117 | if(!this.source) { 118 | throw new Error('call src() before destroy()!'); 119 | } 120 | File.fileList = _.without(File.fileList, this); 121 | }; 122 | 123 | File.prototype.temp = function(isTemporary) { 124 | this.isTemporary = isTemporary; 125 | 126 | return this; 127 | }; 128 | 129 | File.fileList = []; 130 | 131 | File.setup = function(file) { 132 | var bonefs = require('./fs.js').fs; 133 | var pathResolve = function() { 134 | return bonefs.pathResolve.apply(bonefs, arguments); 135 | } 136 | 137 | if(!file) { 138 | file = this.fileList; 139 | } 140 | 141 | if(_.isArray(file)) { 142 | return file.map(File.setup); 143 | } 144 | 145 | file.parentPath = pathResolve(file.parentPath); 146 | file.destPath = pathResolve(file.destination, file.parentPath); 147 | if(file.cwdPath) { 148 | file.cwdPath = pathResolve(file.cwdPath); 149 | } 150 | file.source = _.isArray(file.source) ? file.source : [file.source]; 151 | 152 | _.each(file.source, function(src) { 153 | new FileFormat(src, file); 154 | }); 155 | }; 156 | 157 | function FileFormat(src, file) { 158 | // the File instance 159 | this.file = file; 160 | this.src = src; 161 | // the source file array 162 | this.source = null; 163 | // source file's dir 164 | this.srcDir = ''; 165 | 166 | this.initialize(); 167 | } 168 | 169 | FileFormat.prototype.initialize = function() { 170 | var file = this.file; 171 | 172 | if(file.cwdPath) { 173 | this.source = this.pathResolve(this.src, file.cwdPath); 174 | } else { 175 | this.source = this.pathResolve(this.src, file.destPath); 176 | } 177 | 178 | if(this.hasGlobSyntax()) { 179 | this.srcDir = this.getGlobSearchPath(); 180 | } 181 | 182 | if(this.hasGlobSyntax()) { 183 | this.glob(); 184 | } else { 185 | this.register(); 186 | } 187 | }; 188 | 189 | FileFormat.prototype.glob = function() { 190 | var bonefs = require('./fs.js').fs; 191 | var result = bonefs.search(this.source, {searchAll: true}); 192 | 193 | if(result.length) { 194 | _.each(result, function(source) { 195 | source = this.pathResolve(source); 196 | // it's be supported what virtual file 197 | if(!bonefs.existFile(source, {notFs: true})) { 198 | // filter folder 199 | var stat = fs.statSync(source); 200 | if(!stat.isFile()) return; 201 | } 202 | this.register(source); 203 | }, this); 204 | } else { 205 | // warn 206 | Log.warn('Not exists: ' + this.source + '!'); 207 | } 208 | }; 209 | 210 | FileFormat.prototype.register = function(source) { 211 | var bonefs = FileSystem.fs; 212 | 213 | if(!this.hasGlobSyntax()) { 214 | source = this.source; 215 | if(!bonefs.existFile(source)) { 216 | Log.warn('Not exists: ' + source + '!'); 217 | return; 218 | } 219 | } 220 | 221 | var dest, dir, destPath = this.file.destPath; 222 | 223 | if(this.srcDir) { 224 | dir = path.relative(this.srcDir, path.dirname(source)); 225 | dir = dir.split(path.sep).join('/'); 226 | } else { 227 | dir = ''; 228 | } 229 | 230 | if(this.file.dirFn !== null) { 231 | if(_.isString(this.file.dirFn)) { 232 | dir = this.file.dirFn; 233 | } else if(_.isFunction(this.file.dirFn)) { 234 | dir = this.file.dirFn.call(null, dir, source, destPath); 235 | } 236 | } 237 | 238 | dest = path.join(destPath, dir, path.basename(source)); 239 | 240 | dest = this.rename(source, dest); 241 | 242 | FileSystem.register(dest, { 243 | src: source, 244 | acts: _.clone(this.file.acts), 245 | globalActs: _.clone(this.file.globalActs), 246 | temp: this.file.isTemporary 247 | }); 248 | }; 249 | 250 | FileFormat.prototype.pathResolve = function() { 251 | var bonefs = require('./fs.js').fs; 252 | return bonefs.pathResolve.apply(bonefs, arguments); 253 | }; 254 | 255 | FileFormat.prototype.hasGlobSyntax = function() { 256 | return this.getGlobSearchPath() != this.source; 257 | }; 258 | 259 | FileFormat.prototype.getGlobSearchPath = function() { 260 | if(!this.globSearchPath) { 261 | var mmatch = minimatch.Minimatch(this.source); 262 | var result = []; 263 | var matchArray = mmatch.set[0]; 264 | 265 | for(var i = 0, length = matchArray.length; i < length; i++) { 266 | if(_.isString(matchArray[i])) { 267 | result.push(matchArray[i]); 268 | } else { 269 | break; 270 | } 271 | } 272 | 273 | this.globSearchPath = this.pathResolve(result.join('/')); 274 | } 275 | return this.globSearchPath; 276 | }; 277 | 278 | FileFormat.prototype.rename = function(source, dest) { 279 | var file = this.file; 280 | var fileProperty = path.parse(source); 281 | 282 | if(_.isFunction(file.renameFn)) { 283 | fileProperty.base = file.renameFn.call(null, fileProperty.base, source, _.clone(fileProperty)); 284 | } else if(_.isString(file.renameFn)) { 285 | fileProperty.base = file.renameFn; 286 | } else if(_.isObject(file.renameFn)) { 287 | var renameOption = file.renameFn; 288 | if(renameOption.name) { 289 | fileProperty.name = renameOption.name; 290 | } 291 | if(renameOption.ext && _.isString(renameOption.ext)) { 292 | if(renameOption.ext.indexOf('.') != 0) { 293 | fileProperty.ext = '.'+renameOption.ext; 294 | } else { 295 | fileProperty.ext = renameOption.ext; 296 | } 297 | } else if(renameOption.extTransport && _.isObject(renameOption.extTransport)) { 298 | if(fileProperty.ext in renameOption.extTransport) { 299 | fileProperty.ext = renameOption.extTransport[fileProperty.ext]; 300 | } 301 | } 302 | fileProperty.base = fileProperty.name + fileProperty.ext; 303 | } 304 | fileProperty.dir = path.dirname(dest); 305 | dest = path.format(fileProperty); 306 | 307 | return dest; 308 | }; 309 | 310 | module.exports = File; -------------------------------------------------------------------------------- /lib/fs.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var minimatch = require('minimatch'); 3 | var _ = require('lodash'); 4 | var glob = require('glob'); 5 | var fs = require('fs'); 6 | var aggre = require('akostream').aggre; 7 | var mkdirp = require('mkdirp'); 8 | var Data = require('./data'); 9 | var EventEmitter = require('events'); 10 | 11 | // file path src 12 | function FileSystem(base, options) { 13 | options || (options = {}); 14 | this.base = this.pathResolve(base); 15 | if(options.captureFile) { 16 | this._readFiles = []; 17 | } 18 | if(options.globalAct) { 19 | this.globalAct = options.globalAct; 20 | } 21 | } 22 | 23 | FileSystem.prototype.pathResolve = function(filepath, dir) { 24 | var first = filepath[0], 25 | root = path.resolve('/'), 26 | result; 27 | 28 | if(dir) { 29 | dir = this.pathResolve(dir); 30 | } else { 31 | dir = this.base; 32 | } 33 | 34 | if(filepath.indexOf(root) == 0) { // absolute 35 | result = path.resolve('/', filepath); 36 | } else if(first == '.') { // relative 37 | result = path.resolve(dir, filepath); 38 | } else if(first == '~') { // virtual home 39 | filepath = filepath.replace(/^~+/, ''); 40 | result = path.resolve(path.join(this.base, filepath)); 41 | } else { 42 | result = path.resolve(dir, filepath); 43 | } 44 | 45 | result = path.normalize(result); 46 | result = result.split(path.sep).join('/'); 47 | return result; // unix style 48 | }; 49 | 50 | FileSystem.prototype.createReadStream = function(filePath, options) { 51 | var fileSystem = this; 52 | 53 | options || (options = {}); 54 | options.act = !options.act ? [] : (_.isArray(options.act) ? options.act : [options.act]); 55 | filePath = this.pathResolve(filePath); 56 | if(!this.existFile(filePath)) { 57 | throw new Error('file not exists: '+filePath); 58 | } 59 | 60 | if(this._readFiles) { 61 | this._readFiles.push(filePath); 62 | } 63 | if(this.globalAct) { 64 | if(_.isArray(this.globalAct)) { 65 | options.act = options.act.concat(this.globalAct); 66 | } else { 67 | options.act.push(this.globalAct); 68 | } 69 | } 70 | 71 | var ReadStream = require('./read_stream.js'); 72 | var readStream = new ReadStream(filePath, { 73 | encoding: options.encoding, 74 | onEnd: function() { 75 | if(fileSystem._readFiles) { 76 | fileSystem._readFiles = fileSystem._readFiles.concat(readStream.actsDependent); 77 | } 78 | } 79 | }, options.act); 80 | 81 | return readStream.getStream(); 82 | }; 83 | 84 | FileSystem.prototype.readFile = function(filePath, option, callback) { 85 | var buffer; 86 | var stream; 87 | 88 | if(!callback && _.isFunction(option)) { 89 | callback = option; 90 | option = {}; 91 | } 92 | 93 | try { 94 | stream = this.createReadStream(filePath, option); 95 | 96 | stream.on('error', function(e) { 97 | callback && callback(e); 98 | }); 99 | aggre(stream).on('data', function(chunk) { 100 | buffer = chunk; 101 | }).on('end', function() { 102 | callback && callback(null, buffer || new Buffer(0)); 103 | }); 104 | } catch(e) { 105 | callback && callback(e); 106 | } 107 | }; 108 | 109 | FileSystem.prototype.mkdir = function(dir) { 110 | dir = this.pathResolve(dir); 111 | mkdirp.sync(dir); 112 | }; 113 | 114 | FileSystem.prototype.rm = function(dir) { 115 | dir = this.pathResolve(dir); 116 | 117 | if(dir.indexOf(bone.status.base) == -1) { 118 | throw new Error('Dangerous behavior! you are use "rm" function without project dir.'); 119 | } 120 | 121 | if(fs.existsSync(dir)) { 122 | var stat = fs.statSync(dir); 123 | if(stat.isDirectory()) { 124 | var contain = fs.readdirSync(dir); 125 | _.each(contain, function(file) { 126 | this.rm(this.pathResolve(file, dir)); 127 | }, this); 128 | fs.rmdirSync(dir); 129 | } else { 130 | fs.unlinkSync(dir); 131 | } 132 | } 133 | }; 134 | 135 | FileSystem.prototype.createWriteStream = function(filePath, option) { 136 | option || (option = {}); 137 | filePath = this.pathResolve(filePath); 138 | var dir = path.dirname(filePath); 139 | if(option.focus) { 140 | this.mkdir(dir); 141 | } else { 142 | if(!fs.existsSync(dir)) { 143 | throw new Error('fail to open '+dir); 144 | } 145 | } 146 | return fs.createWriteStream(filePath, option); 147 | }; 148 | 149 | FileSystem.prototype.writeFile = function(filePath, content, option) { 150 | option || (option = {}); 151 | filePath = this.pathResolve(filePath); 152 | var stream = this.createWriteStream(filePath, option); 153 | 154 | stream.write(content); 155 | stream.end(); 156 | }; 157 | 158 | FileSystem.prototype.search = function(search, option) { 159 | option || (option = {}); 160 | search = this.pathResolve(search); 161 | var result = minimatch.match(Data.virtualFileStack, search, {}); 162 | 163 | if(!option.notFs) { 164 | var real = glob.sync(search); 165 | 166 | result = result.concat(real); 167 | } 168 | var self = this; 169 | result = result.map(function(file) { 170 | return self.pathResolve(file); 171 | }); 172 | if(!option.searchAll) { 173 | var tempFiles = _.clone(Data.virtualTemporaryFile); 174 | if(result.length) { 175 | tempFiles.unshift(result); 176 | result = _.without.apply(_, tempFiles); 177 | } 178 | } 179 | result = _.uniq(result); 180 | return result; 181 | }; 182 | 183 | FileSystem.prototype.refresh = function() { 184 | return FileSystem.refresh(); 185 | }; 186 | 187 | FileSystem.prototype.readDir = function(dirPath) { 188 | dirPath = this.pathResolve(dirPath); 189 | var result = this.search(path.join(dirPath, '*')); 190 | 191 | result = result.map(function(file) { 192 | return path.relative(dirPath, file).split(path.sep).join('/'); 193 | }); 194 | return result; 195 | }; 196 | 197 | FileSystem.prototype.existFile = function(filePath, option) { 198 | option || (option = {}); 199 | 200 | filePath = this.pathResolve(filePath); 201 | 202 | if(filePath in Data.virtualFiles) { 203 | return true; 204 | } else { 205 | if(!option.notFs) { 206 | filePath = this.pathResolve(filePath); 207 | if(fs.existsSync(filePath)) { 208 | var stat = fs.statSync(filePath); 209 | 210 | return stat.isFile(); 211 | } 212 | } 213 | } 214 | return false; 215 | }; 216 | 217 | /** 218 | * static 219 | */ 220 | Object.defineProperty(FileSystem, 'fs', { 221 | configurable: false, 222 | enumerable: true, 223 | get: function() { 224 | if(!this.value) { 225 | this.value = new FileSystem(bone.status.base); 226 | } 227 | return this.value; 228 | } 229 | }); 230 | 231 | FileSystem.event = new EventEmitter(); 232 | 233 | FileSystem.setTraceTree = function(file, dependencies) { 234 | var realDependencies = []; 235 | var raw = dependencies; 236 | 237 | while(raw.length) { 238 | var checkFile = raw.pop(); 239 | if(Data.virtualFileTraceTree[checkFile]) { 240 | raw = raw.concat(Data.virtualFileTraceTree[checkFile]); 241 | } else { 242 | realDependencies.push(checkFile); 243 | } 244 | } 245 | 246 | realDependencies = _.uniq(realDependencies); 247 | 248 | Data.virtualFileTraceTree[file] = realDependencies; 249 | // 构建反向依赖树 250 | _.each(realDependencies, function(f) { 251 | if(!Data.virtualFileTraceTreeB[f]) { 252 | Data.virtualFileTraceTreeB[f] = []; 253 | } 254 | if(_.indexOf(Data.virtualFileTraceTreeB[f], file) == -1) { 255 | Data.virtualFileTraceTreeB[f].push(file); 256 | } 257 | }); 258 | this.event.emit('setTraceTree', file); 259 | }; 260 | FileSystem.register = function(file, options) { 261 | file = this.fs.pathResolve(file); 262 | if(_.has(Data.virtualFiles, file)) { 263 | // warn 264 | throw new Error('Register override:'+file); 265 | } 266 | Data.virtualFileStack.push(file); 267 | Data.virtualFiles[file] = _.extend(options, {dest: file}); 268 | if(options.temp) { 269 | Data.virtualTemporaryFile.push(file); 270 | } 271 | }; 272 | 273 | FileSystem.refresh = function() { 274 | Data.virtualFiles = {}; 275 | Data.virtualFileStack.length = 0; 276 | Data.virtualTemporaryFile.length = 0; 277 | 278 | var File = require('./file.js'); 279 | File.setup(); 280 | this.searchStack(); 281 | }; 282 | 283 | FileSystem.searchStack = function() { 284 | var stack = {}; 285 | var files = _.keys(Data.virtualFiles); 286 | _.each(files, function(file) { 287 | stack[file] = true; 288 | while(true) { 289 | var dir = path.dirname(file); 290 | if(stack[dir]) break; 291 | if(dir == '/') break; 292 | if(dir == bone.status.base) break; 293 | stack[dir] = true; 294 | file = dir; 295 | } 296 | }, this); 297 | Data.virtualFileStack = _.keys(stack); 298 | }; 299 | 300 | FileSystem.getFs = function(options) { 301 | if(options) { 302 | return new FileSystem(bone.status.base, options); 303 | } else { 304 | return FileSystem.fs; 305 | } 306 | }; 307 | 308 | 309 | module.exports = FileSystem; -------------------------------------------------------------------------------- /test/bone/bonefile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var AKOStream = require('akostream'); 3 | var combine = AKOStream.combine; 4 | var aggre = AKOStream.aggre; 5 | var bone = require('../../index.js'); 6 | 7 | var plugins = { 8 | author: bone.require('../bone/plugins_author.js'), 9 | copyright: bone.require('../bone/plugins_copyright.js'), 10 | dependency: bone.require('../bone/dependency.js'), 11 | dependencyArray: bone.require('../bone/dependency_array.js'), 12 | error: bone.require('../bone/callback_error.js'), 13 | concat: bone.require('bone-act-concat'), 14 | less: bone.require('bone-act-less'), 15 | }; 16 | 17 | // define a virtual folder 'dist' 18 | var dist = bone.dest('dist'); 19 | 20 | // rename 21 | var renameDir = dist.dest('rename'); 22 | 23 | renameDir 24 | .src('~/src/rename/base.js') 25 | .rename('foo.js'); 26 | 27 | renameDir 28 | .src('~/src/rename/base.js') 29 | .rename({ 30 | ext: 'jsx' 31 | }); 32 | 33 | renameDir 34 | .src('~/src/rename/base.js') 35 | .rename({ 36 | name: 'bar' 37 | }); 38 | 39 | renameDir 40 | .src('~/src/rename/base.js') 41 | .rename({ 42 | name: 'bar', 43 | ext: '.jsx' 44 | }); 45 | 46 | renameDir 47 | .src('~/src/rename/base.js') 48 | .rename(function() { 49 | return 'zoo.js' 50 | }); 51 | 52 | renameDir 53 | .src('~/src/rename/base.js') 54 | .rename({ 55 | extTransport: { 56 | '.js': '.transport' 57 | } 58 | }); 59 | 60 | // define single file 61 | var singleDir = dist.dest('single'); 62 | 63 | singleDir 64 | .src('~/src/single/foo.js'); 65 | 66 | singleDir 67 | .src('./foo.js') 68 | .rename('zoo.js'); 69 | 70 | bone.dest('dist/single').src('~/src/single/foo.js').rename('bar.js'); 71 | 72 | // use src() glob 73 | var globDir = dist.dest('glob'); 74 | 75 | globDir.src('~/src/glob/**/*'); 76 | 77 | // duplicate definition 78 | var duplicateDefinitionDir = dist.dest('duplicateDefinition'); 79 | 80 | duplicateDefinitionDir.src('~/src/duplicateDefinition/foo.js'); 81 | 82 | // over reference 83 | var overReferencesDir = bone.dest('src/overReferences'); 84 | 85 | overReferencesDir.src('./foo.js'); 86 | 87 | // all empty file 88 | var emptyDir = dist.dest('empty'); 89 | 90 | emptyDir.src('~/src/empty/*'); 91 | 92 | // temp dir 93 | var tempDir = dist.dest('temp'); 94 | 95 | tempDir 96 | .temp(true) 97 | .src('~/src/temp/*.js'); 98 | 99 | // read dir 100 | var readDirDir = dist.dest('readDir'); 101 | 102 | readDirDir 103 | .src('~/src/readDir/*'); 104 | 105 | readDirDir 106 | .src('~/src/readDir/*') 107 | .rename(function(filename) { 108 | return filename + '.jsx'; 109 | }); 110 | 111 | // rependencyFile 112 | var dependencyFileDir = dist.dest('dependencyFile'); 113 | 114 | dependencyFileDir 115 | .src('~/src/dependencyFile/foo.js') 116 | .act(plugins.concat({ 117 | files: [ 118 | '~/src/dependencyFile/dependency_a.js', 119 | '~/src/dependencyFile/dependency_b.js' 120 | ] 121 | })) 122 | .rename('dependency.js'); 123 | 124 | dependencyFileDir 125 | .src('~/src/dependencyFile/foo.js') 126 | .act(plugins.concat({ 127 | files: [ 128 | '~/src/dependencyFile/dependency_a.js', 129 | '~/src/dependencyFile/dependency_b.js', 130 | '~/src/dependencyFile/dependency_c.js' 131 | ] 132 | })) 133 | .rename('dependency_2.js'); 134 | 135 | dependencyFileDir 136 | .src('./dependency.js') 137 | .act(plugins.concat({ 138 | files: [ 139 | '~/src/dependencyFile/dependency_c.js' 140 | ] 141 | })) 142 | .rename('dependency_3.js'); 143 | 144 | // deleteFile dir 145 | var deleteFileDir = dist.dest('deleteFile'); 146 | 147 | deleteFileDir 148 | .src('~/src/deleteFile/foo.js') 149 | .act(plugins.concat({ 150 | files: [ 151 | '~/src/deleteFile/concat/*.js' 152 | ] 153 | })); 154 | 155 | // cwd dir 156 | var cwdDir = dist.dest('cwd'); 157 | 158 | cwdDir 159 | .cwd('~/src/cwd') 160 | .src('./**/*'); 161 | 162 | // change dir 163 | var changeDir = dist.dest('change'); 164 | 165 | changeDir 166 | .src('~/src/change/change.js'); 167 | 168 | // plugins dir 169 | var pluginsDir = dist.dest('plugins').cwd('~/src/plugins'); 170 | 171 | pluginsDir 172 | .src('~/src/plugins/foo.js') 173 | .act(plugins.author) 174 | .rename('author_not_parameter.js'); 175 | 176 | pluginsDir 177 | .src('~/src/plugins/foo.js') 178 | .act(plugins.copyright()) 179 | .rename('copyright.js'); 180 | 181 | pluginsDir 182 | .src('~/src/plugins/foo.js') 183 | .act(plugins.author({ 184 | author: 'wyicwx' 185 | })) 186 | .act(plugins.copyright({ 187 | copyright: 'wyicwx' 188 | })) 189 | .rename('author_copyright.js'); 190 | 191 | pluginsDir 192 | .src('~/src/plugins/foo.js') 193 | .act(plugins.author({ 194 | author: 'wyicwx' 195 | })) 196 | .rename('author.js'); 197 | 198 | pluginsDir 199 | .src('~/src/plugins/foo.js') 200 | .act(plugins.dependency) 201 | .rename('addDependency.js'); 202 | 203 | pluginsDir 204 | .src('~/src/plugins/foo.js') 205 | .act(plugins.dependencyArray) 206 | .rename('addDependencyArray.js'); 207 | 208 | pluginsDir 209 | .src('./foo.js') 210 | .act(plugins.error) 211 | .rename('error.js'); 212 | 213 | // track dir 214 | var trackDir = dist.dest('track'); 215 | 216 | trackDir 217 | .src('~/dist/single/zoo.js') 218 | .rename('bar.js'); 219 | 220 | trackDir 221 | .src('~/dist/single/foo.js'); 222 | 223 | var lessDir = dist.dest('less'); 224 | 225 | lessDir 226 | .src('~/src/less/style.less') 227 | .act(plugins.less) 228 | .rename('style.css'); 229 | 230 | lessDir 231 | .src('~/src/less/style.less') 232 | .act(plugins.less({}, { 233 | filter: { 234 | name: 'less' 235 | } 236 | })) 237 | .rename('notCompile.js') 238 | 239 | var dirDir = dist.dest('dir'); 240 | 241 | dirDir 242 | .src('~/src/dir/foo.js') 243 | .dir('string'); 244 | 245 | dirDir 246 | .src('~/src/dir/foo.js') 247 | .dir(function(dir, source, destination) { 248 | return 'function'; 249 | }); 250 | 251 | dirDir 252 | .src('~/src/dir/**/*.js') 253 | .dir(''); 254 | 255 | 256 | // dev.dest('track') 257 | // .src('~/dev/js/*'); 258 | 259 | // dev.dest('trackRename') 260 | // .src('../track/hello.js') 261 | // .rename('foo.js'); 262 | 263 | // define over reference file 264 | // dist.dest('js') 265 | // .src('./hello.js') 266 | // .rename('a.js'); 267 | // dist.dest('js') 268 | // .src('./a.js') 269 | // .rename('b.js'); 270 | 271 | 272 | 273 | // map dist 274 | // var cdist = bone.dest('cdist'); 275 | // cdist.src('~/dist/**/*') 276 | // .act(plugins.less); 277 | 278 | // define a virtual folder 'dev' 279 | // var dev = bone.dest('dev'); 280 | // map ~/src/js/*.js to dev 281 | // dev.dest('js') 282 | // .src('~/src/js/*.js'); 283 | // map ~/src/css/css.css to dev/css.css 284 | // bone.dest('dev').src('~/src/css/css.css'); 285 | // define ~/dev/js/hello.js pass through author() processor 286 | // dev.dest('js') 287 | // .src('./hello.js') 288 | // .act(plugins.author({ 289 | // author: 'wyicwx' 290 | // })) 291 | // .rename('hello_sign.js'); 292 | // define ~/dev/js/hello.js pass through author() and copyright() processor 293 | // dev.dest('js') 294 | // .src('./hello.js') 295 | // .act(plugins.author({ 296 | // author: 'wyicwx' 297 | // })) 298 | // .act(plugins.copyright({ 299 | // copyright: 'wyicwx' 300 | // })) 301 | // .rename('hello_sign_copyright.js'); 302 | 303 | // define ~/dev/js/hello.js pass through copyright() processor 304 | // dev.dest('js') 305 | // .src('./hello.js') 306 | // .act(plugins.copyright()) 307 | // .rename('hello_copyright_default.js'); 308 | 309 | // dev.dest('js') 310 | // .src('./hello.js') 311 | // .act(plugins.author) 312 | // .rename('hello_sign-noparam.js'); 313 | 314 | // dev.dest('track') 315 | // .src('~/dev/js/*'); 316 | 317 | // dev.dest('trackRename') 318 | // .src('../track/hello.js') 319 | // .rename('foo.js'); 320 | 321 | // dev.dest('dependentFile') 322 | // .src('~/dev/js/hello.js') 323 | // .act(plugins.concat({ 324 | // files: [ 325 | // '~/src/project/file1.js', 326 | // '~/dev/css.css' 327 | // ] 328 | // })); 329 | 330 | // dev.dest('dependentFile') 331 | // .src('~/dev/js/hello.js') 332 | // .act(plugins.concat({ 333 | // files: [ 334 | // '~/src/project/file2.js', 335 | // '~/src/project/file3.js', 336 | // '~/dev/css.css' 337 | // ] 338 | // })) 339 | // .rename('foo.js'); 340 | 341 | // dev.dest('dependentFile') 342 | // .src('~/dev/js/hello.js') 343 | // .act(plugins.concat({ 344 | // files: [ 345 | // '~/src/js/*.js' 346 | // ] 347 | // })) 348 | // .rename('concatGlob.js'); 349 | 350 | // dev.dest('change') 351 | // .src('~/src/js/change.js'); 352 | 353 | // dev.dest('change') 354 | // .src('~/src/js/added.js'); 355 | 356 | // bone.dest('cwd/all') 357 | // .cwd('~/src') 358 | // .src('./**/*'); 359 | 360 | // bone.dest('cwd/js') 361 | // .cwd('~/src') 362 | // .src('./js/**/*'); 363 | 364 | // bone.dest('cwd/folder') 365 | // .cwd('~/src') 366 | // .src('js/hello.js'); 367 | 368 | // define a virtual folder 'search' for test search() 369 | // bone.dest('search') 370 | // .src('~/src/**/*'); 371 | 372 | // bone.dest('notExist') 373 | // .src('./*/notExist.js'); -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | var bone = require('../index.js'); 4 | var path = require('path'); 5 | var glob = require('glob'); 6 | var Stream = require('stream').Stream; 7 | var _ = require('lodash'); 8 | var fs = require('fs'); 9 | var os = require('os'); 10 | var FileSystem = require('../lib/fs.js'); 11 | var Data = require('../lib/data.js'); 12 | var bonefs; 13 | 14 | bone.setup('./test/raw'); 15 | bonefs = FileSystem.fs; 16 | 17 | require('./bone/bonefile.js'); 18 | 19 | bone.status.test = true; 20 | describe('bone.setup', function() { 21 | it('correct', function() { 22 | assert.doesNotThrow(function() { 23 | bone.run(); 24 | }); 25 | }); 26 | }); 27 | 28 | describe('bone.dest', function() { 29 | it('dest() define a parent folder when do not call src()', function() { 30 | var result = bonefs.existFile('~/dist/single/foo.js', { 31 | notFs: true 32 | }); 33 | 34 | if (result) { 35 | assert.ok(true); 36 | } else { 37 | assert.ok(false); 38 | } 39 | }); 40 | 41 | it('dest() define a folder when call src()', function() { 42 | var result = bonefs.existFile('~/dist/single/bar.js', { 43 | notFs: true 44 | }); 45 | if (result) { 46 | assert.ok(true); 47 | } else { 48 | assert.ok(false); 49 | } 50 | }); 51 | 52 | it('dest() pass string parameter only', function() { 53 | assert.throws(function() { 54 | bone.dest({}); 55 | }); 56 | 57 | assert.throws(function() { 58 | bone.dest(true); 59 | }); 60 | 61 | assert.throws(function() { 62 | bone.dest(1); 63 | }); 64 | 65 | assert.throws(function() { 66 | bone.dest(function() {}); 67 | }); 68 | }); 69 | 70 | it('src() pass string or string array parameter only', function() { 71 | assert.throws(function() { 72 | bone.dest('~/dist') 73 | .src({}); 74 | }); 75 | 76 | assert.throws(function() { 77 | bone.dest('~/dist') 78 | .src([null, undefined]); 79 | }); 80 | }); 81 | 82 | it('single file define', function() { 83 | var exist = bonefs.existFile('~/dist/single/foo.js'); 84 | 85 | if (exist) { 86 | assert.ok(true); 87 | } else { 88 | assert.ok(false); 89 | } 90 | }); 91 | 92 | it('use glob to map file of define file', function() { 93 | var exist = bonefs.existFile('~/dist/glob/foo.js'); 94 | 95 | if (exist) { 96 | assert.ok(true); 97 | } else { 98 | assert.ok(false); 99 | } 100 | }); 101 | 102 | it('rename() is runing correct', function() { 103 | if(!bonefs.existFile('~/dist/rename/foo.js')) { 104 | assert.ok(false); 105 | } 106 | if(!bonefs.existFile('~/dist/rename/base.jsx')) { 107 | assert.ok(false); 108 | } 109 | if(!bonefs.existFile('~/dist/rename/bar.js')) { 110 | assert.ok(false); 111 | } 112 | if(!bonefs.existFile('~/dist/rename/bar.jsx')) { 113 | assert.ok(false); 114 | } 115 | 116 | if(!bonefs.existFile('~/dist/rename/zoo.js')) { 117 | assert.ok(false); 118 | } 119 | 120 | if(!bonefs.existFile('~/dist/rename/base.transport')) { 121 | assert.ok(false); 122 | } 123 | }); 124 | 125 | it('rename() pass string, function and object parameter only!', function() { 126 | var define = bone.dest('dist/rename').src('~/src/rename/base.js'); 127 | 128 | assert.throws(function() { 129 | define.rename(1); 130 | }); 131 | 132 | define.destroy(); 133 | }); 134 | 135 | it('act() process source file', function(done) { 136 | bonefs.readFile('~/dist/plugins/author.js', function(err, buffer) { 137 | var content = buffer.toString(); 138 | if (~content.search('@author wyicwx')) { 139 | done(); 140 | } else { 141 | done(false); 142 | } 143 | }); 144 | }); 145 | 146 | it('multi act() process source file', function(done) { 147 | bonefs.readFile('~/dist/plugins/author_copyright.js', function(err, buffer) { 148 | var content = buffer.toString(); 149 | 150 | if (~content.search('@copyright wyicwx')) { 151 | done(); 152 | } else { 153 | done(false); 154 | } 155 | }); 156 | }); 157 | 158 | it('call rename(), dir(), act() and destroy() before src() will throw error', function() { 159 | assert.throws(function() { 160 | bone.dest('dist') 161 | .rename(); 162 | }); 163 | 164 | assert.throws(function() { 165 | bone.dest('dist') 166 | .act(); 167 | }); 168 | 169 | assert.throws(function() { 170 | bone.dest('dist') 171 | .destroy(); 172 | }); 173 | 174 | assert.throws(function() { 175 | bone.dest('dist') 176 | .dir(); 177 | }); 178 | }); 179 | 180 | it('act() processor without parameter', function(done) { 181 | bonefs.readFile('~/dist/plugins/author_not_parameter.js', function(err, buffer) { 182 | var content = buffer.toString(); 183 | if (~content.search('@author anonymous')) { 184 | done(); 185 | } else { 186 | done(false); 187 | } 188 | }); 189 | }); 190 | 191 | it('cwd() change work directory', function() { 192 | var cwdret = bonefs.search('~/dist/cwd/all/**/*'); 193 | var origret = bonefs.search('~/src/cwd/all/**/*'); 194 | 195 | if (cwdret.length === origret.length) { 196 | assert.ok(true); 197 | } else { 198 | assert.ok(false); 199 | } 200 | }); 201 | 202 | it('cwd() pass string parameter only', function() { 203 | assert.throws(function() { 204 | bone.dest('dist/cwd') 205 | .cwd({}); 206 | }); 207 | 208 | assert.throws(function() { 209 | bone.dest('dist/cwd') 210 | .cwd(function() {}); 211 | }); 212 | 213 | assert.throws(function() { 214 | bone.dest('dist/cwd') 215 | .cwd(true); 216 | }); 217 | 218 | assert.throws(function() { 219 | bone.dest('dist/cwd') 220 | .cwd(1); 221 | }); 222 | }); 223 | 224 | it('dir() call to change destination\'s subfolder', function() { 225 | if(!bonefs.existFile('~/dist/dir/string/foo.js')) { 226 | assert.ok(false); 227 | } 228 | if(!bonefs.existFile('~/dist/dir/function/foo.js')) { 229 | assert.ok(false); 230 | } 231 | }); 232 | 233 | it('dir() call function with empty string', function() { 234 | if(!bonefs.existFile('~/dist/dir/foo.js')) { 235 | assert.ok(false); 236 | } 237 | if(!bonefs.existFile('~/dist/dir/bar.js')) { 238 | assert.ok(false); 239 | } 240 | }); 241 | 242 | it('dir() pass string or function parameter only', function() { 243 | assert.throws(function() { 244 | bone.dest('dist/dir/parameter') 245 | .src('~/src/dir/foo.js') 246 | .dir({}); 247 | }); 248 | 249 | assert.throws(function() { 250 | bone.dest('dist/dir/parameter') 251 | .src('~/src/dir/glob/bar.js') 252 | .dir(false); 253 | }); 254 | 255 | assert.throws(function() { 256 | bone.dest('dist/dir/parameter') 257 | .src('~/src/dir/glob/zoo.js') 258 | .dir(1); 259 | }); 260 | }); 261 | 262 | it("temp() should be not show in search's result", function() { 263 | var result = bonefs.search('~/temp/*'); 264 | if (result.length) { 265 | assert.ok(false); 266 | } 267 | }); 268 | 269 | it('temp() utils.fs.getAllVirtualFiles should be not show in result', function() { 270 | var result = bone.utils.fs.getAllVirtualFiles(); 271 | 272 | result = _.filter(result, function(filePath) { 273 | if (filePath.indexOf('temp') != -1) { 274 | return true; 275 | } else { 276 | return false; 277 | } 278 | }); 279 | 280 | if (result.length) { 281 | assert.ok(false); 282 | } 283 | }); 284 | 285 | it('throw error when call cwd() after src()!', function() { 286 | var define = bone.dest('cwd/src').src('~/src/**'); 287 | 288 | assert.throws(function() { 289 | define.cwd('~/src'); 290 | }); 291 | 292 | define.destroy(); 293 | bonefs.refresh(); 294 | }); 295 | 296 | it('throw error define over file', function() { 297 | var file; 298 | assert.throws(function() { 299 | file = bone.dest('dist/duplicateDefinition') 300 | .src('~/src/duplicateDefinition/foo.js'); 301 | 302 | bonefs.refresh(); 303 | }); 304 | file.destroy(); 305 | bonefs.refresh(); 306 | }); 307 | 308 | it('throw error when over reference file', function(done) { 309 | bonefs.readFile('~/src/overReferences/foo.js', function(error, data) { 310 | if (error) { 311 | if(error.message.indexOf('File over references') != -1) { 312 | var File = require('../lib/file.js'); 313 | _.each(File.fileList, function(item) { 314 | if (item.destination == 'src/overReferences') { 315 | File.fileList = _.without(File.fileList, item); 316 | } 317 | }); 318 | done(); 319 | } else { 320 | done(false); 321 | } 322 | } else { 323 | done(false); 324 | } 325 | }); 326 | }); 327 | 328 | it('src() set a path what file no exists should show warning log', function() { 329 | var dist = bone.dest('dist') 330 | .dest('notExists') 331 | .cwd('~/src'); 332 | 333 | // glob 334 | var globNotExists = dist.src('notExists/**/*'); 335 | bonefs.refresh(); 336 | if(Data.logInfo.pop().indexOf('Not exists:') == -1) { 337 | assert.ok(false); 338 | } 339 | 340 | // definite path 341 | var fooNotExists = dist.src('notExists/foo.js'); 342 | bonefs.refresh(); 343 | if(Data.logInfo.pop().indexOf('Not exists:') == -1) { 344 | assert.ok(false); 345 | } 346 | 347 | globNotExists.destroy(); 348 | fooNotExists.destroy(); 349 | bonefs.refresh(); 350 | }); 351 | }); 352 | 353 | describe('bone.fs', function() { 354 | describe('createReadStream', function() { 355 | it('read a exist or defined file will return a stream obj', function() { 356 | var stream = bonefs.createReadStream('~/dist/single/foo.js'); 357 | if (stream instanceof Stream) { 358 | assert.ok(true); 359 | } else { 360 | assert.ok(false); 361 | } 362 | }); 363 | 364 | it('read a not exist file will throw error', function() { 365 | assert.throws(function() { 366 | bonefs.createReadStream('~/dist/not/exist/file.js'); 367 | }); 368 | }); 369 | 370 | it('read a empty file return a valid stream(will trigger "end" event)', function(done) { 371 | var stream = bonefs.createReadStream('~/dist/empty/js.js'); 372 | var avild = true; 373 | stream.on('data', function() { 374 | avild = false; 375 | }); 376 | 377 | stream.on('end', function() { 378 | if (avild) { 379 | done(); 380 | } else { 381 | done(false); 382 | } 383 | }); 384 | }); 385 | 386 | it('read a virtual file is viable', function(done) { 387 | var stream = bonefs.createReadStream('~/dist/temp/foo.js'); 388 | 389 | stream.on('data', function() { 390 | done(); 391 | }); 392 | }); 393 | 394 | it('read a file what mapping from virtual file', function() { 395 | if (bonefs.existFile('dist/single/zoo.js', { 396 | notFs: true 397 | })) { 398 | assert.ok(true); 399 | } else { 400 | assert.ok(false); 401 | } 402 | }); 403 | 404 | it('setting single globalAct for fs', function(done) { 405 | var fs = FileSystem.getFs({ 406 | globalAct: bone.wrapper(function(buffer, encoding, callback) { 407 | callback(null, buffer.toString() + '1'); 408 | }) 409 | }); 410 | 411 | var avild = true; 412 | 413 | var stream = fs.createReadStream('~/src/js/hello.js'); 414 | 415 | stream.on('data', function(trunk) { 416 | if (trunk.toString() != "alert('hello world!');1") { 417 | avild = false; 418 | } 419 | }); 420 | 421 | stream.on('end', function() { 422 | if (avild) { 423 | done(); 424 | } else { 425 | done(false); 426 | } 427 | }); 428 | }); 429 | 430 | it('setting multi globalAct for fs', function(done) { 431 | var fs = FileSystem.getFs({ 432 | globalAct: [bone.wrapper(function(buffer, encoding, callback) { 433 | callback(null, buffer.toString() + '1'); 434 | }), 435 | bone.wrapper(function(buffer, encoding, callback) { 436 | callback(null, buffer.toString() + '2'); 437 | }) 438 | ] 439 | }); 440 | 441 | var avild = true; 442 | 443 | var stream = fs.createReadStream('~/src/js/hello.js'); 444 | 445 | stream.on('data', function(trunk) { 446 | if (trunk.toString() != "alert('hello world!');12") { 447 | avild = false; 448 | } 449 | }); 450 | 451 | stream.on('end', function() { 452 | if (avild) { 453 | done(); 454 | } else { 455 | done(false); 456 | } 457 | }); 458 | }); 459 | }); 460 | 461 | describe('pathResolve', function() { 462 | it('separator only / characters are used! on windows characters \\ will be converted into / ', function() { 463 | if (os.platform().indexOf('win') == 0) { 464 | var result = bonefs.pathResolve('~\\test\\characters'); 465 | 466 | if (result == bonefs.pathResolve(path.join(bonefs.base, '/test/characters'))) { 467 | assert.ok(true); 468 | } else { 469 | assert.ok(false); 470 | } 471 | } else { 472 | assert.ok(true); 473 | } 474 | }); 475 | 476 | it('resolve ~ to bone\'s base path', function() { 477 | var resolveResult = bonefs.pathResolve('~'); 478 | 479 | if (resolveResult == bonefs.base) { 480 | assert.ok(true); 481 | } else { 482 | assert.ok(false); 483 | } 484 | }); 485 | 486 | it('resolve ~folder to ~/folder', function() { 487 | var cresult = bonefs.pathResolve('~folder'); 488 | var sresult = bonefs.pathResolve('~/folder'); 489 | 490 | if (cresult == sresult) { 491 | assert.ok(true); 492 | } else { 493 | assert.ok(false); 494 | } 495 | }); 496 | 497 | it('resolve / to absolute path', function() { 498 | var resolveResult = bonefs.pathResolve('/'); 499 | 500 | if (resolveResult == bonefs.pathResolve(path.resolve('/'))) { 501 | assert.ok(true); 502 | } else { 503 | assert.ok(false); 504 | } 505 | }); 506 | 507 | it('resolve . default to bone\'s base path', function() { 508 | var resolveResult = bonefs.pathResolve('.'); 509 | 510 | if (resolveResult == bonefs.base) { 511 | assert.ok(true); 512 | } else { 513 | assert.ok(false); 514 | } 515 | }); 516 | 517 | it('resolve . to relative some path', function() { 518 | var resolveResult = bonefs.pathResolve('./test', '/example'); 519 | 520 | if (resolveResult == bonefs.pathResolve(path.resolve('/example/test'))) { 521 | assert.ok(true); 522 | } else { 523 | assert.ok(false); 524 | } 525 | }); 526 | 527 | it('resolve "" default to bone\'s base path', function() { 528 | var resolveResult = bonefs.pathResolve(''); 529 | 530 | if (resolveResult == bonefs.base) { 531 | assert.ok(true); 532 | } else { 533 | assert.ok(false); 534 | } 535 | }); 536 | 537 | it('support resolve ~ dir path', function() { 538 | var resolveResult = bonefs.pathResolve('.', '~'); 539 | 540 | if (resolveResult == bonefs.base) { 541 | assert.ok(true); 542 | } else { 543 | assert.ok(false); 544 | } 545 | }); 546 | }); 547 | 548 | describe('createWriteStream', function() { 549 | it('create write stream under not exist folder will throw a error', function() { 550 | assert.throws(function() { 551 | bonefs.rm('~/dist/not/exist'); 552 | bonefs.createWriteStream('~/dist/not/exist/File.js'); 553 | }); 554 | }); 555 | 556 | it('use focus parameter to create folder and create write stream', function() { 557 | var dir = '~/dist/not/exist'; 558 | var file = path.join(dir, '/file.js'); 559 | 560 | bonefs.rm(dir); 561 | 562 | assert.doesNotThrow(function() { 563 | var stream = bonefs.createWriteStream(file, { 564 | focus: true 565 | }); 566 | stream.write('\r\n'); 567 | stream.end(); 568 | fs.existsSync(file); 569 | }); 570 | }); 571 | }); 572 | 573 | describe('readFile', function() { 574 | it('read a not exist file will throw error', function(done) { 575 | var file = '~/dist/not/exist/file.js'; 576 | bonefs.rm(file); 577 | bonefs.readFile(file, function(error, data) { 578 | if (error) { 579 | done(); 580 | } else { 581 | done(false); 582 | } 583 | }); 584 | }); 585 | 586 | it('read a empty file', function(done) { 587 | var content = fs.readFileSync(bonefs.pathResolve('~/src/empty/js.js')); 588 | bonefs.readFile('~/dist/empty/js.js', function(err, c) { 589 | if (content.toString() == c.toString()) { 590 | done(); 591 | } else { 592 | done(false); 593 | } 594 | }); 595 | }); 596 | 597 | it('read a virtual file as same as real file', function(done) { 598 | var content = fs.readFileSync(bonefs.pathResolve('~/src/single/foo.js')); 599 | 600 | bonefs.readFile('~/dist//single/foo.js', function(err, result) { 601 | if (result.toString() == content.toString()) { 602 | done(); 603 | } else { 604 | done(false); 605 | } 606 | }); 607 | }); 608 | 609 | it('read file with option that has act', function(done) { 610 | var act = bone.wrapper(function(buffer, encoding, callback) { 611 | var ctx = buffer.toString(); 612 | 613 | ctx += '|test'; 614 | 615 | callback(null, ctx); 616 | }); 617 | 618 | bonefs.readFile('~/src/js/hello.js', { 619 | act: act 620 | }, function(err, buffer) { 621 | if (err) { 622 | return done(false); 623 | } 624 | 625 | if (buffer.toString() != "alert('hello world!');|test") { 626 | return done(false); 627 | } 628 | 629 | bonefs.readFile('~/src/js/hello.js', function(err, buffer) { 630 | if (buffer.toString() == "alert('hello world!');") { 631 | done(); 632 | } else { 633 | done(false); 634 | } 635 | }); 636 | }); 637 | }); 638 | }); 639 | 640 | describe('writeFile', function() { 641 | it('write file and create folder', function() { 642 | bonefs.rm('~/folder'); 643 | bonefs.writeFile('~/folder/foo.js', 'test', { 644 | focus: true 645 | }); 646 | 647 | if (fs.existsSync(bonefs.pathResolve('~/folder/foo.js'))) { 648 | process.nextTick(function() { 649 | bonefs.rm('~/folder'); 650 | }); 651 | assert.ok(true); 652 | } else { 653 | assert.ok(false); 654 | } 655 | }); 656 | }); 657 | 658 | describe('search', function() { 659 | it('result correct', function() { 660 | var vresult = bonefs.search('~/src/**/*'); 661 | var rresult = glob.sync(bonefs.pathResolve('~/src/**/*')); 662 | 663 | if (_.intersection(vresult, rresult).length != rresult.length) { 664 | assert.ok(false); 665 | } 666 | }); 667 | }); 668 | 669 | describe('readDir', function() { 670 | it('read virtual folder', function() { 671 | var content = bonefs.readDir('~/dist/readDir'); 672 | var vcontent = fs.readdirSync(bonefs.pathResolve('~/src/readDir')); 673 | 674 | if (_.intersection(content, vcontent).length != vcontent.length) { 675 | assert.ok(false); 676 | } 677 | }); 678 | }); 679 | 680 | describe('mkdir', function() { 681 | it('depend on the mkdirp libraries, only test ~ mkdir', function() { 682 | bonefs.rm('~/mkdir'); 683 | bonefs.mkdir('~/mkdir'); 684 | var dir = bonefs.pathResolve('~/mkdir'); 685 | try { 686 | var stat = fs.statSync(dir); 687 | if (stat.isDirectory()) { 688 | assert.ok(true); 689 | } else { 690 | assert.ok(false); 691 | } 692 | } catch (e) { 693 | assert.ok(false); 694 | } 695 | bonefs.rm('~/mkdir'); 696 | }); 697 | }); 698 | 699 | describe('rm', function() { 700 | it('support recursive delete file and folder', function() { 701 | bonefs.mkdir('~/rm/subdir'); 702 | var file1 = bonefs.pathResolve('~/rm/toRm.js'); 703 | fs.writeFileSync(file1, 'test'); 704 | var file2 = bonefs.pathResolve('~/rm/subdir/toRm.js'); 705 | fs.writeFileSync(file2, 'test'); 706 | 707 | bonefs.rm('~/rm'); 708 | var dir = bonefs.pathResolve('~/rm'); 709 | if (fs.existsSync(dir)) { 710 | assert.ok(false); 711 | } else { 712 | assert.ok(true); 713 | } 714 | }); 715 | 716 | it('rm dir without project base will throw a error', function() { 717 | assert.throws(function() { 718 | bonefs.rm('/tmp'); 719 | }); 720 | }); 721 | }); 722 | 723 | describe('refresh', function() { 724 | it('to refresh glob match file(not exist before)', function() { 725 | var file = bonefs.pathResolve('~/src/glob/afterAdd.js'); 726 | var vfile = bonefs.pathResolve('~/dist/glob/afterAdd.js'); 727 | fs.writeFileSync(file, 'test'); 728 | bonefs.refresh(); 729 | var exist = bonefs.existFile(vfile); 730 | bonefs.rm(file); 731 | bonefs.refresh(); 732 | if (exist) { 733 | assert.ok(true); 734 | } else { 735 | assert.ok(false); 736 | } 737 | }); 738 | }); 739 | }); 740 | 741 | describe('bone.watch', function() { 742 | 743 | it('run away', function() { 744 | assert.doesNotThrow(function() { 745 | var watcher = bone.watch(); 746 | 747 | bone.watch(); 748 | }); 749 | }); 750 | 751 | it('change file will clean cache', function(done) { 752 | var cache = require('../lib/cache.js'); 753 | var filePath = bonefs.pathResolve('~/dist/change/change.js'); 754 | var sourcePath = bonefs.pathResolve('~/src/change/change.js'); 755 | 756 | bone.watch(); 757 | 758 | fs.writeFileSync(sourcePath, '1'); 759 | bonefs.readFile(filePath, function(error, buffer) { 760 | var cached = cache.get(filePath); 761 | 762 | if (!cache.get(filePath)) { 763 | return done(false); 764 | } 765 | 766 | setTimeout(function() { 767 | fs.writeFileSync(sourcePath, 'change file'); 768 | setTimeout(function() { 769 | if (cache.get(filePath)) { 770 | return done(false); 771 | } 772 | 773 | done(); 774 | bone.status.watch = false; 775 | }, 500); 776 | }, 500); 777 | 778 | }); 779 | }); 780 | 781 | it('add or delete file will clean cache and refresh file system', function(done) { 782 | var cache = require('../lib/cache.js'); 783 | var addFile = bonefs.pathResolve('~/src/change/add.js'); 784 | 785 | if (fs.existsSync(addFile)) { 786 | fs.unlinkSync(addFile); 787 | } 788 | 789 | bone.watch(); 790 | 791 | bonefs.readFile('~/dist/change/change.js', function(error, buffer) { 792 | var filePath = bonefs.pathResolve('~/dist/change/change.js'); 793 | 794 | if (!cache.get(filePath)) { 795 | return done(false); 796 | } 797 | fs.writeFile(addFile, 'add file', function(err) { 798 | if (err) { 799 | return done(false); 800 | } 801 | setTimeout(function() { 802 | var result = false; 803 | if (!cache.get(filePath)) { 804 | result = null; 805 | } 806 | fs.unlinkSync(addFile); 807 | 808 | done(result); 809 | }, 600); 810 | }); 811 | }); 812 | }); 813 | 814 | it('delete file', function(done) { 815 | bone.watch(); 816 | 817 | var addFile = bonefs.pathResolve('~/src/deleteFile/concat/temp.js'); 818 | 819 | fs.writeFile(addFile, 'test', function() { 820 | setTimeout(function() { 821 | bone.utils.fs.dependentFile('~/dist/deleteFile/foo.js', function(err, dependenciesA) { 822 | setTimeout(function() { 823 | fs.unlink(addFile, function() { 824 | setTimeout(function() { 825 | bone.utils.fs.dependentFile('~/dist/deleteFile/foo.js', function(err, dependenciesB) { 826 | var diff = _.difference(dependenciesA, dependenciesB); 827 | 828 | if (diff.length == 1 && diff[0] == addFile) { 829 | done(); 830 | } else { 831 | done(false); 832 | } 833 | }); 834 | }, 500); 835 | }); 836 | }, 500); 837 | }); 838 | }, 500); 839 | }); 840 | }); 841 | }); --------------------------------------------------------------------------------