├── .prettierrc ├── lib └── util.js ├── .npmignore ├── .gitignore ├── command ├── util.js └── run.js ├── package.json ├── bin └── yog2 ├── README.md └── yog2-fis3.js /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "singleQuote": true, 6 | "semi": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": false, 9 | "quoteProps": "consistent", 10 | "arrowParens": "avoid", 11 | "jsxBracketSameLine": false, 12 | "overrides": [ 13 | { 14 | "files": ["*.js"], 15 | "options": { 16 | "spaceBeforeFunctionParen": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | module.exports.checkProject = function () { 7 | var cwd = process.cwd(); 8 | try { 9 | fs.statSync(path.join(cwd, 'package.json')); 10 | var packageInfo = require(path.join(cwd, 'package.json')); 11 | if (packageInfo.scripts.debug && packageInfo.scripts['debug-win']) { 12 | return true; 13 | } 14 | } 15 | catch (e) { 16 | return false; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Webstorm 2 | .idea 3 | 4 | # Logs 5 | /logs 6 | /log 7 | /test/log 8 | *.log 9 | 10 | # Tmp 11 | /test/tmp 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directory 31 | # Commenting this out is preferred by some people, see 32 | # https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 33 | node_modules 34 | 35 | # Users Environment Variables 36 | .lock-wscript 37 | 38 | # test 39 | test 40 | 41 | # benchmark 42 | benchmark -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Deploy 2 | _site/** 3 | /conf/app/** 4 | /conf/fis/** 5 | /static/** 6 | /views/** 7 | /app/** 8 | 9 | .modules 10 | 11 | # Webstorm 12 | .idea 13 | 14 | # Logs 15 | /logs 16 | /log 17 | /test/log 18 | *.log 19 | 20 | # Tmp 21 | /test/tmp 22 | /tmp 23 | 24 | # Runtime data 25 | pids 26 | *.pid 27 | *.seed 28 | 29 | # Directory for instrumented libs generated by jscoverage/JSCover 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | coverage 34 | 35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 36 | .grunt 37 | 38 | # Compiled binary addons (http://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directory 42 | # Commenting this out is preferred by some people, see 43 | # https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 44 | node_modules 45 | 46 | # Users Environment Variables 47 | .lock-wscript 48 | 49 | # headdump 50 | *.heapsnapshot 51 | -------------------------------------------------------------------------------- /command/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var spawn = require('child_process').spawn; 5 | var yogUtil = require('../lib/util.js'); 6 | 7 | exports.name = 'util'; 8 | exports.desc = 'yog2 util'; 9 | exports.register = function (commander) { 10 | var pac = require('yog-pac'); 11 | commander.option('--fis3', 'fis3 mode', Boolean, false) 12 | commander.command('pack').description('pack node_modules for submit'); 13 | commander.command('unpack').description('unpack node_modules'); 14 | commander.action(function () { 15 | if (!yogUtil.checkProject()) { 16 | fis.log.error('current folder is not a valid yog project'.red); 17 | } 18 | fis.log.throw = true; 19 | var args = Array.prototype.slice.call(arguments); 20 | var options = args.pop(); 21 | var cmd = args.shift(); 22 | if (!cmd) { 23 | commander.outputHelp(); 24 | return; 25 | } 26 | 27 | var strategy = new pac.NpmStrategy({ 28 | mode: 'production', 29 | verbose: true 30 | }); 31 | if (cmd === 'pack') { 32 | strategy.pack(); 33 | } 34 | else if (cmd === 'unpack') { 35 | strategy.install(); 36 | } 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yog2", 3 | "version": "1.7.5", 4 | "description": "Front End Integrated Solution for node express.", 5 | "main": "yog2.js", 6 | "bin": { 7 | "yog2": "bin/yog2" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/fex-team/yog2.git" 15 | }, 16 | "keywords": [ 17 | "fis", 18 | "node" 19 | ], 20 | "author": "fex-team", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/fex-team/yog2/issues" 24 | }, 25 | "homepage": "https://github.com/fex-team/yog2", 26 | "dependencies": { 27 | "fis-parser-bdtmpl": "0.0.3", 28 | "fis-parser-less": "0.1.3", 29 | "fis3": "3.4.46", 30 | "fis3-hook-commonjs": "0.1.32", 31 | "fis3-hook-node_modules": "2.3.1", 32 | "fis3-parser-typescript": "1.5.0", 33 | "fis3-preprocessor-js-require-css": "0.1.3", 34 | "fis3-preprocessor-js-require-file": "0.1.3", 35 | "liftoff": "2.3.0", 36 | "minimist": "1.2.5", 37 | "yog-pac": "0.1.0", 38 | "yog2-command-init": "0.3.x", 39 | "yog2-command-plugin": "0.1.x", 40 | "yogurt-postprocessor-require-async": "0.0.x", 41 | "yogurt-preprocessor-extlang": "0.0.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /bin/yog2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var Liftoff = require('liftoff'); 4 | var argv = require('minimist')(process.argv.slice(2)); 5 | var path = require('path'); 6 | var fs = require('fs'); 7 | var cli = new Liftoff({ 8 | name: 'yog2', // 命令名字 9 | processTitle: 'yog2', 10 | moduleName: 'yog2/yog2-fis3.js', 11 | configName: 'fis-conf', 12 | 13 | // only js supported! 14 | extensions: { 15 | '.js': null 16 | } 17 | }); 18 | 19 | for (var i = process.argv.length - 1; i >= 0; i--) { 20 | if (process.argv[i] === '--fis2') { 21 | process.argv.splice(i, 1); 22 | } 23 | } 24 | 25 | cli.launch( 26 | { 27 | cwd: argv.r || argv.root, 28 | configPath: argv.f || argv.file 29 | }, 30 | function (env) { 31 | var fis; 32 | 33 | try { 34 | var conf = fs.readFileSync(env.configPath).toString(); 35 | if ( 36 | conf.match(/\/\*\s?fis3-enable\s?\*\//gim) || 37 | conf.match(/\/\/\s?fis3-enable/gim) 38 | ) { 39 | argv.fis3 = true; 40 | } 41 | } catch (e) {} 42 | 43 | delete argv.F; 44 | delete argv.fis3; 45 | delete argv.fis2; 46 | 47 | if (!env.modulePath) { 48 | fis = require('../yog2-fis3.js'); 49 | } else { 50 | fis = require(env.modulePath); 51 | } 52 | fis.IS_FIS3 = true; 53 | fis.require.paths.unshift(path.join(env.cwd, 'node_modules')); 54 | fis.require.paths.push(path.join(path.dirname(__dirname), 'node_modules')); 55 | fis.require.paths.push( 56 | path.join( 57 | path.join( 58 | path.dirname(__dirname), 59 | 'node_modules', 60 | 'fis3', 61 | 'node_modules' 62 | ) 63 | ) 64 | ); 65 | fis.cli.run(argv, env); 66 | } 67 | ); 68 | -------------------------------------------------------------------------------- /command/run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var spawn = require('child_process').spawn; 5 | var yogUtil = require('../lib/util.js'); 6 | 7 | exports.name = 'run'; 8 | exports.desc = 'run yog2 server with a simple daemon'; 9 | exports.register = function (commander) { 10 | commander 11 | .option('--fis3', 'fis3 mode', Boolean, false) 12 | .option('-n, --nodebug', 'run server without debug mode', Boolean, false) 13 | .option('-e, --env [env]', 'set YOG_ENV', String, 'dev') 14 | .action(function () { 15 | if (!yogUtil.checkProject()) { 16 | fis.log.error('current folder is not a valid yog project'.red); 17 | } 18 | var options = arguments[arguments.length - 1]; 19 | fis.log.throw = true; 20 | start(options); 21 | }); 22 | }; 23 | 24 | function start(options) { 25 | var server; 26 | var env = process.env; 27 | env.YOG_ENV = options.env || ''; 28 | if (options.nodebug) { 29 | server = spawn('node', ['app'], { 30 | env: env 31 | }); 32 | } 33 | else { 34 | var isWin = /^win/.test(process.platform); 35 | if (isWin) { 36 | server = spawn('npm.cmd', ['run', 'debug-win'], { 37 | env: env 38 | }); 39 | } 40 | else { 41 | server = spawn('npm', ['run', 'debug'], { 42 | env: env 43 | }); 44 | } 45 | } 46 | server.stdout.pipe(process.stdout); 47 | server.stderr.pipe(process.stderr); 48 | server.on('exit', function (code) { 49 | process.exit(); 50 | fis.log.warning(('yog2 server exit with code ' + code + ', restarting...').yellow); 51 | start(options); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YOG2 2 | 3 | yog2 是一个专注于 Node.js UI 中间层的应用框架。它基于 express 和 fis 开发,在享受 express 的灵活扩展能力和 fis 强大的前端工程化能力的同时,引入了自动路由、app 拆分以及后端服务管理模块来保证UI中间层的快速开发与稳定可靠。 4 | 5 | ------------------ 6 | 7 | ## 兼容性 8 | 9 | - [x] node 0.8.x 10 | - [x] node 0.10.x 11 | - [x] node 0.12.x 12 | - [x] io.js 13 | 14 | 同时支持 fis 与 fis3 两种编译核心 15 | 16 | ## 入门指引 17 | 18 | ### 安装yog2 19 | 20 | > 需要先安装 [node](http://nodejs.org). 21 | 22 | ```bash 23 | npm install -g yog2 24 | ``` 25 | 26 | ### 创建project 27 | 28 | yog2 project是基础的运行框架,提供一些基础的配置和中间件管理。通过使用 yog2 提供的脚手架,可以快速创建一个基础的 yog2 project 29 | 30 | ```bash 31 | yog2 init project 32 | # prompt: Enter your project name: (yog) 33 | ``` 34 | 35 | ### 创建app 36 | 37 | yog2 app 是应用的业务代码,每一个 app 都是一个独立的子项目,包含了这个子项目中所有的前后端代码。我们可以利用 yog2 release 功能将 app 发布至 yog2 project 中来运行 app。 38 | 39 | 利用 yog2 的 app 拆分能力,我们可以将一个中大型规模的项目按照功能或业务划分为多个独立的 app ,每个 app 均可以独立开发、编译、部署。当项目的业务较简单时,也可以只使用一个 app 来管理代码。 40 | 41 | ```bash 42 | yog2 init app 43 | # prompt: Enter your app name: (home) 44 | ``` 45 | 46 | ### 开发调试 47 | 48 | #### 安装依赖 49 | 50 | 首先我们需要为 yog2 project 安装执行必须的依赖 51 | 52 | ```bash 53 | # 进入 yog project 目录 54 | cd yog 55 | npm install 56 | ``` 57 | 58 | #### 启动框架 59 | 60 | 然后我们就可以用开发调试模式启动 yog2 project,让运行框架可用 61 | 62 | > 切勿在生产环境使用开发调试模式启动 yog2 project,这样的行为将会引发安全问题。 63 | 64 | ```bash 65 | yog2 run 66 | ``` 67 | 68 | yog2 project 的默认端口是 8085,你可以通过修改 `PORT` 环境变量或者直接修改 `app.js` 来指定端口。 69 | 70 | 此时如果我们访问 `http://127.0.0.1:8085` 由于我们并未部署应用,我们只会得到一个 404 页面。因此下一步我们就需要部署 app。 71 | 72 | #### 部署app 73 | 74 | 由于启动 yog2 project 后会一直占用控制台,因此我们需要另外开启一个控制台去部署 app。 75 | 76 | ```bash 77 | # 进入home目录 78 | cd home 79 | yog2 release --dest debug 80 | ``` 81 | 82 | > yog2 release --dest debug 必须要求运行框架以调试模式启动后使用,否则无法正确的部署代码。 83 | 84 | 再次访问 `http://127.0.0.1:8085` 我们就会看到网站已经正常提供服务了。 85 | 86 | 此外,如果我们在执行 yog2 release 命令时添加 `--watch` 参数,yog2 就会监听文件修改,并自动部署至 yog2 project 。通过 yog2 的热更新技术,只要是 app 中的代码,无论是静态资源还是后端模板亦或是后端逻辑,均无需重启 yog2 project 就可以生效。 87 | 88 | ```bash 89 | yog2 release --dest dev --watch 90 | ``` 91 | 92 | ## 文档 93 | 94 | 请查阅 [官网](http://fex.baidu.com/yog2) 95 | -------------------------------------------------------------------------------- /yog2-fis3.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fis = module.exports = require('fis3'); 4 | fis.require.prefixes.unshift('yog2'); 5 | fis.require.prefixes.unshift('yogurt'); 6 | fis.cli.name = 'yog2'; 7 | fis.cli.info = require('./package.json'); 8 | 9 | fis.set('modules.commands', ['init', 'install', 'release', 'run', 'inspect', 'util']); 10 | 11 | fis.set('template', '/views'); 12 | fis.set('app', '/app'); 13 | fis.set('static', '/static'); 14 | fis.set('config', '/conf'); 15 | fis.set('component.dir', '/client/components'); 16 | fis.set('project.fileType.text', 'es,ts,tsx,jsx'); 17 | fis.set('project.ignore', [ 18 | 'issue.info', 19 | 'README.md', 20 | 'BCLOUD', 21 | 'GIT_COMMIT', 22 | 'fis.yml', 23 | 'cooder', 24 | 'build.sh', 25 | 'component.json', 26 | 'output/**', 27 | '/client/node_modules/**', 28 | 'fis-conf.js' 29 | ]); 30 | 31 | var clientRoadmap = { 32 | // all release to $static dir 33 | '/client/(**)': { 34 | id: '$1', 35 | moduleId: '${namespace}:$1', 36 | release: '/${static}/${namespace}/$1' 37 | }, 38 | '/client/**.less': { 39 | parser: fis.plugin('less'), 40 | rExt: '.css' 41 | }, 42 | '/client/{**.ts,**.tsx,**.jsx,**.es}': { 43 | parser: fis.plugin('typescript', { 44 | module: 1, 45 | target: 0, 46 | sourceMap: true 47 | }), 48 | rExt: 'js' 49 | }, 50 | '/client/**.tpl': { 51 | preprocessor: fis.plugin('extlang'), 52 | postprocessor: fis.plugin('require-async'), 53 | useMap: true 54 | }, 55 | '/client/**.{tpl,js,ts,jsx,es,tsx}': { 56 | useSameNameRequire: true 57 | }, 58 | '/client/page/**.tpl': { 59 | extras: { 60 | isPage: true 61 | } 62 | }, 63 | '/client/(page/**.tpl)': { 64 | url: '${namespace}/$1', 65 | release: '/${template}/${namespace}/$1', 66 | useMap: true 67 | }, 68 | '/client/(widget/**.tpl)': { 69 | url: '${namespace}/$1', 70 | release: '/${template}/${namespace}/$1', 71 | useMap: true 72 | }, 73 | '/client/{components,widget}/**.{js,es,ts,tsx,jsx,css,less}': { 74 | isMod: true 75 | }, 76 | '/client/test/(**)': { 77 | useMap: false, 78 | release: '/test/${namespace}/$1' 79 | }, 80 | '${namespace}-map.json': { 81 | release: '${config}/fis/${namespace}-map.json' 82 | }, 83 | '::package': {} 84 | }; 85 | 86 | var commonRoadmap = { 87 | '**.sh': { 88 | release: false 89 | }, 90 | '**': { 91 | release: '${static}/${namespace}/$0' 92 | } 93 | }; 94 | 95 | var serverRoadmap = { 96 | '/server/(**)': { 97 | useMap: false, 98 | preprocessor: false, 99 | standard: false, 100 | postprocessor: false, 101 | optimizer: false, 102 | useHash: false, 103 | useDomain: false, 104 | isMod: false, 105 | release: '${app}/${namespace}/$1' 106 | }, 107 | '/server/{**.ts,**.es}': { 108 | parser: fis.plugin('typescript', { 109 | module: 1, 110 | target: 2, 111 | sourceMap: true 112 | }), 113 | rExt: 'js' 114 | }, 115 | '/{node_modules/**,package.json}': { 116 | useCompile: false, 117 | release: 'app/${namespace}/$0' 118 | } 119 | }; 120 | 121 | var prodRoadmap = { 122 | '/client/**.{js,css,less,ts,jsx,es,tsx}': { 123 | useHash: true 124 | }, 125 | '/client/**.{js,ts,jsx,es,tsx}': { 126 | optimizer: fis.plugin('uglify-js') 127 | }, 128 | '/client/**.{css,less}': { 129 | optimizer: fis.plugin('clean-css') 130 | }, 131 | '::image': { 132 | useHash: true 133 | } 134 | }; 135 | 136 | // 添加自定义命令 137 | fis.require._cache['command-run'] = require('./command/run.js'); 138 | fis.require._cache['command-util'] = require('./command/util.js'); 139 | 140 | [commonRoadmap, clientRoadmap, serverRoadmap, prodRoadmap].forEach(function(roadmap) { 141 | fis.util.map(roadmap, function(selector, rules) { 142 | fis.match(selector, rules); 143 | }); 144 | }); 145 | 146 | // 发布模式关闭sourceMap 147 | fis.media('prod').match('/client/{**.ts,**.tsx,**.jsx,**.es}', { 148 | parser: fis.plugin('typescript', { 149 | module: 1, 150 | target: 0 151 | }), 152 | rExt: 'js' 153 | }).match('/server/{**.ts,**.es}',{ 154 | parser: fis.plugin('typescript', { 155 | module: 1, 156 | target: 2 157 | }), 158 | rExt: 'js' 159 | }); 160 | 161 | fis.enableES7 = function (options) { 162 | [fis.media('dev'), fis.media('debug'), fis.media('debug-prod')].forEach(function (media) { 163 | media.match('/server/**.js', { 164 | parser: fis.plugin('typescript', { 165 | module: 1, 166 | target: 2, 167 | sourceMap: true 168 | }) 169 | }); 170 | }); 171 | fis.match('/server/**.js', { 172 | parser: fis.plugin('typescript', { 173 | module: 1, 174 | target: 2 175 | }) 176 | }); 177 | }; 178 | 179 | fis.enableNPM = function (options) { 180 | options = options || {}; 181 | fis.match('/client/node_modules/**.js', { 182 | isMod: true 183 | }); 184 | if (options.autoPack) { 185 | fis.match('/client/node_modules/**.js', { 186 | packTo: options.npmBundlePath || '/client/pkg/npm/bundle.js' 187 | }); 188 | fis.match('/client/node_modules/**.css', { 189 | packTo: options.npmCssBundlePath || '/client/pkg/npm/bundle.css' 190 | }); 191 | fis.on('deploy:start', function(groups) { 192 | groups.forEach(function(group) { 193 | var modified = group.modified; 194 | var total = group.modified; 195 | var file; 196 | var i = modified.length - 1; 197 | while ((file = modified[i--])) { 198 | if ((file.rExt === '.js' || file.rExt === '.css') && file.subpath.indexOf('/client/node_modules') === 0) { 199 | modified.splice(i + 1, 1); 200 | } 201 | } 202 | i = total.length - 1; 203 | while ((file = total[i--])) { 204 | if ((file.rExt === '.js' || file.rExt === '.css') && file.subpath.indexOf('/client/node_modules') === 0) { 205 | total.splice(i + 1, 1); 206 | } 207 | } 208 | }); 209 | }); 210 | } 211 | fis.match('/client/**.{js,es,jsx,ts,tsx}', { 212 | preprocessor: [ 213 | fis.plugin('js-require-file'), 214 | fis.plugin('js-require-css') 215 | ] 216 | }); 217 | fis.unhook('components'); 218 | fis.hook('node_modules', options.node_modules || { 219 | shimGlobal: false, 220 | shimProcess: false, 221 | shimBuffer: false 222 | }); 223 | }; 224 | 225 | // 模块化支持 226 | fis.hook('commonjs', { 227 | extList: ['.js', '.es', '.ts', '.tsx', '.jsx'] 228 | }); 229 | 230 | // map.json 231 | fis.match('::package', { 232 | postpackager: function createMap(ret, conf, settings, opt) { 233 | var maps = {}; 234 | fis.util.map(ret.src, function(subpath, file) { 235 | maps[file.id] = file; 236 | }); 237 | var pkgMaps = {}; 238 | fis.util.map(ret.pkg, function(subpath, file) { 239 | pkgMaps[file.getUrl()] = file; 240 | }); 241 | var path = require('path'); 242 | var root = fis.project.getProjectPath(); 243 | var map = fis.file.wrap(path.join(root, fis.get('namespace') + '-map.json')); 244 | var resKeys = Object.keys(ret.map.res); 245 | var pkgKeys = Object.keys(ret.map.pkg); 246 | for (var i = 0; i < resKeys.length; i++) { 247 | var resId = resKeys[i]; 248 | if (maps[resId]) { 249 | ret.map.res[resId].subpath = maps[resId].getHashRelease(); 250 | } else { 251 | fis.log.warning(resId + ' is missing'); 252 | } 253 | } 254 | for (var j = 0; j < pkgKeys.length; j++) { 255 | var pkg = ret.map.pkg[pkgKeys[j]]; 256 | if (pkgMaps[pkg.uri]) { 257 | pkg.subpath = pkgMaps[pkg.uri].getHashRelease(); 258 | } else { 259 | fis.log.warning(pkg.uri + ' is missing'); 260 | } 261 | } 262 | map.setContent(JSON.stringify(ret.map, null, 4)); 263 | ret.pkg[map.subpath] = map; 264 | } 265 | }); 266 | --------------------------------------------------------------------------------