背景目的 自从139邮箱移动端酷版邮箱构建工具由原来的 ant 转移到 gulp 之后,构建速度大大的提升,就拿本人的机子(本人工作机子是32位6G内存)来测试,之前构建全量包要花费将近 20 分钟,如今只需要 4 分钟不到;另外 gulp 是基于 node.js 的,对前端开发来说是相当友好的。但是,还是有点美中不足:第一,比如像我这样的前端小白,我对命令行不是很敏感,甚至我不太喜欢敲一串串的命令行去执行一个任务,我希望能有一个图形化界面工具,能点点按钮什么的就可以完成一个构建任务,那该多爽。第二,目前的 gulp 构建还不支持自定义构建,这个自定义构建其实就是,比如说,我想打包某个目标文件(这个文件可能由好几个文件合并压缩而成),只要选择了这个文件的文件名,然后点击按钮,就可以帮你执行的任务,帮你构建好这个文件,甚至可以帮你部署到资源服务器上。
简单的说就是:
基于以上两个目的,所以就做了一个这样的图形化前端构建小工具。
效果展示
全量构建
JavaScript 增量构建
运用技术
目前的 gulp 构建通过命令行可执行独立任务任务有
1 2 3 4 5 gulp gulp deploy gulp uploadStaticFiles gulp restartNodeServer gulp watch
说明:以上的命令除了部署并重启测试线的 node 服务之外,其他的任务都将加到本次的小工具上。
最重要的东西要来了,它就是 Electron。在这之前,大家应该都有听说过 Node-Webkit (后期改名 NW.js)。NW.js 允许您直接从 DOM 层调用所有 Node.js 模块,并允许使用所有Web技术编写 PC 端的应用程序。而 Electron 也差不多类似这样的一种工具或者说框架。
Electron 是允许使用 JavaScript,HTML 和 CSS 等 Web 技术创建 PC 应用程序的框架。 它负责跟系统打交道,使得开发者可以更加专注于应用本身。Electron 官网 http://electron.atom.io/ 。
Electron 的主要特点有:
Web 技术。 Electron 是基于 Chromium 和 Node.js,因此您可以使用HTML,CSS和JavaScript构建应用程序。
开源。 Electron是由GitHub和活跃的贡献者社区维护的开源项目。
跨平台。 兼容 Mac,Windows 和 Linux 系统,Electron 应用程序可在三个平台上构建和运行。
Electron 快速入手:
1 2 3 4 5 6 7 8 $ git clone https://github.com/electron/electron-quick-start $ cd electron-quick-start $ npm install && npm start
我为什么要用 Electron 而不用 NW.js ?
好奇心的我,想接触一下新事物;
剩下的理由主要是受了知乎一些吐槽的影响。《用Nodejs开发桌面应用。NW.js 和 Electron 各有什么优缺点,你选择哪个? 》
有哪些公司或者 App 在用 Electron?
其实,Electron 已经被微软,Facebook,Slack 和 Docker 等公司用来创建应用程序。成功案例有很多,比较有代表性的有如下这些:
Atom 编辑器
Slack(那个独角兽公司)
Visual Studio Code
WordPress 桌面版
UI 界面用的是 Bootstrap,简洁、直观、强悍的前端开发框架,让web开发更迅速、简单。
由于本图形构建工具功能比较简单,所以想做一个单页面就好,但是又不想用像 vue 这样的框架,网上找了一下,发现 Q.js 这个路由框架。
Q.js 是一个炒鸡轻量的前端单页路由框架。官网地址是 http://mouto.org/#!54092 ,Github 地址是 https://github.com/itorr/q.js 。
Q.js 特点是轻量、快速、极简。为了更好的利用缓存以及更少的后端支援,Q.js放弃了 HTML5 State,通过#!格式的 url hach 重现了 url 路由功能。
无 JavaScript 库依托,可随意搭配使用;
源代码不及百行压缩后 834byte ;
支持 IE6+ Chrome Safari FF (其实 Electron 算是很新的浏览器内核,已经没必要考虑这一点);
未做情况判定,使用 Q.js 必然会注册 window.Q 。
来一段简单的 Hello, World
来简单演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <!doctype> <html > <head > <meta charset ="UTF-8" > <title > 演示</title > <script src ="a.js" > </script > </head > <body > <div id ="m" > </div > <script > Q.reg('home' , function ( ) { document .getElementById('m' ).innerHTML = 'Hello, World!' ; }); Q.init({ index: 'home' }); </script > </body > </html >
打开例子后,浏览器会从 http://simple.com/
跳转到 http://simple.com/#!home
,并且在页面显示 Hello World。
技术实现 首先,我们整体看一下,整个应用的目录结构:
在执行 gulp 各个任务,主要用了 node.js 的 child_process 进程模块的 spawn 方法。spawn
使用如下:
1 2 const spawn = require ('child_process' ).spawn;const gulpTask = spawn('gulp' , [ 'default' ]);
对自定义构建,主要通过命令行传参的方式指定的 gulp 构建的 json 配置,从而实现文件的自定义构建,代码如下:
1 2 3 4 5 let yargv = require ('yargs' ).argv;var fileConf = yargv.fileConf;
全量构建里面包含“构建全量包”、“构建并部署”、“构建并监听”三个任务。实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 $E('btnBuildGlobal' ).onclick = function ( ) { executeBuild({ processingBarId: 'btnBuildGlobalProgress' , processingName: '构建全量包' , command: 'gulp' }); }; $E('btnBuildDeploy' ).onclick = function ( ) { executeBuild({ processingBarId: 'btnBuildDeployProgress' , processingName: '构建并部署' , command: 'gulp' , args: [ 'deploy' ] }); }; $E('btnBuildWatch' ).onclick = function ( ) { var _this = this ; if (_this.dataset.watching === 'off' ) { if (processingObject.processingGulp) return ; closeTips(); processingObject.processingGulp = spawn('gulp' , [ 'watch' ], { cwd: config[$E('btnSwitchRDlines' ).dataset['switch' ]] }); processingObject.processingName = '构建并监听' ; processingObject.processingBarId = 'btnBuildWatchProgress' ; _this.innerHTML = '点击不监听' ; _this.dataset.watching = 'on' ; $E('btnBuildWatchProgress' ).style.width = '100%' ; processingObject.processingGulp.stdout.on('data' , (data ) => { console .log(`stdout: ${data} ` ); }); processingObject.processingGulp.on('close' , (data ) => { console .log(`stdout close: ${data} ` ); if (data === null ) { _this.innerHTML = '构建并监听' ; _this.dataset.watching = 'off' ; } }); } else if (_this.dataset.watching === 'on' ) { _this.innerHTML = '构建并监听' ; _this.dataset.watching = 'off' ; $E('btnBuildWatchProgress' ).style.width = '0%' ; showTips('代码监听已取消!' , 'alert-danger' ); killCurrentProcessing(); } };
JavaScript 自定义构建包含“打包”、“打包并部署”两个任务,实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 $E('btnBuildJS' ).onclick = function ( ) { var oCheckeds = $E('jsBuildContainerContent' ).querySelectorAll('input[type="checkbox"]:checked' ); console .log('oCheckeds =>' , oCheckeds); if (!oCheckeds.length) { alert('请选择要打包的 js 文件!' ); return ; } let fileConf = []; for (var i = 0 , len = oCheckeds.length; i < len; ++i) { fileConf.push(oCheckeds[i].value); } console .log('fileConf =>' , fileConf.join(',' )); executeBuild({ processingBarId: 'btnBuildProgressJS' , processingName: 'JS文件自定义构建' , command: 'gulp' , args: [ 'customBuildJs' , '--fileConf=' + fileConf.join(',' ) ] }); }; $E('btnBuildDeployJS' ).onclick = function ( ) { var oCheckeds = $E('jsBuildContainerContent' ).querySelectorAll('input[type="checkbox"]:checked' ); console .log('oCheckeds =>' , oCheckeds); if (!oCheckeds.length) { alert('请选择要打包部署的 js 文件' ); return ; } let fileConf = []; for (var i = 0 , len = oCheckeds.length; i < len; ++i) { fileConf.push(oCheckeds[i].value); } console .log('fileConf =>' , fileConf.join(',' )); executeBuild({ processingBarId: 'btnBuildProgressJS' , processingName: 'JS文件自定义构建并自动部署' , command: 'gulp' , args: [ 'customBuildAndDeployJs' , '--fileConf=' + fileConf.join(',' ) ] }); }; gulp.task('customBuildJs' , [ 'clean' , 'compileTsFiles' , 'updateJsConcatConfig' ], function ( ) { console .log(`yargv.fileConf => ${yargv.fileConf} ` ); var fileConf = yargv.fileConf; return pump([ concatJsFiles({ concatConfig: fileConf.split(',' ) }), debug({title : "concating --> " }), uglify(), debug({title : "uglifying --> " }), gulp.dest( path.join(destDir)) ]); }); gulp.task('customBuildAndDeployJs' , [ 'customBuildJs' ], function ( ) { let conn = ftp.create( { host: '此处是host ip' , user: 'root' , password: '此处是密码' , parallel: 10 , log: gutil.log } ); gutil.log("----------**********-----------" ); gutil.log(gutil.colors.magenta("---------上传静态资源文件--------" )); gutil.log("----------**********-----------" ); return gulp.src( path.join( resourceDir, "**/*" ), { buffer : false } ) .pipe( conn.dest('/home/richmail/nginx/htdocs/html5' ) ); });
对于 JavaScript 自定义构建,在 gulpfile.js 里面新加了 customBuildJs、customBuildAndDeployJs 两个 task。
CSS 自定义构建包含“打包”、“打包并部署”两个任务,实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 $E('btnBuildCSS' ).onclick = function ( ) { var oCheckeds = $E('cssBuildContainerContent' ).querySelectorAll('input[type="checkbox"]:checked' ); console .log('oCheckeds =>' , oCheckeds); if (!oCheckeds.length) { alert('请选择要打包的 css 文件配置' ); return ; } let fileConf = []; for (var i = 0 , len = oCheckeds.length; i < len; ++i) { fileConf.push(oCheckeds[i].value); } console .log('fileConf =>' , fileConf.join(',' )); executeBuild({ processingBarId: 'btnBuildProgressCSS' , processingName: 'CSS文件自定义构建' , command: 'gulp' , args: [ 'customBuildCss' , '--fileConf=' + fileConf.join(',' ) ] }); }; $E('btnBuildDeployCSS' ).onclick = function ( ) { var oCheckeds = $E('cssBuildContainerContent' ).querySelectorAll('input[type="checkbox"]:checked' ); console .log('oCheckeds =>' , oCheckeds); if (!oCheckeds.length) { alert('请选择要打包的 css 文件配置' ); return ; } let fileConf = []; for (var i = 0 , len = oCheckeds.length; i < len; ++i) { fileConf.push(oCheckeds[i].value); } console .log('fileConf =>' , fileConf.join(',' )); executeBuild({ processingBarId: 'btnBuildProgressCSS' , processingName: 'CSS文件自定义构建并自动部署' , command: 'gulp' , args: [ 'customBuildAndDeployCss' , '--fileConf=' + fileConf.join(',' ) ] }); }; gulp.task('customBuildCss' , [ 'clean' ], function ( ) { console .log(`yargv.fileConf => ${yargv.fileConf} ` ); var fileConf = yargv.fileConf; return concatCssFiles({ concatConfig : fileConf }) .pipe(debug({title : 'concating css file --> ' })) .pipe(replaceImageVersion({ rootDir: html5Dir, images: path.join(html5Dir, '/**/*.{png,gif,jpg,ico}' ) })) .pipe(debug({title : 'img url reversion file --> ' })) .pipe(minifyCss({processImport : false })) .pipe(debug({title : 'compress css file --> ' })) .pipe(gulp.dest(path.join(resourceDir, 'css' ))); }); gulp.task('customBuildAndDeployCss' , [ 'customBuildCss' ], function ( ) { let conn = ftp.create( { host: '此处是host ip' , user: 'root' , password: '此处是密码' , parallel: 10 , log: gutil.log } ); gutil.log("----------**********-----------" ); gutil.log(gutil.colors.magenta("---------上传静态资源文件--------" )); gutil.log("----------**********-----------" ); return gulp.src( path.join( resourceDir, "**/*" ), { buffer : false } ) .pipe( conn.dest('/home/richmail/nginx/htdocs/html5' ) ); });
对于 CSS 自定义构建,在 gulpfile.js 里面新加了 customBuildCss、customBuildAndDeployCss 两个 task。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 $E('btnBuildHTML' ).onclick = function ( ) { var oCheckeds = $E('htmlBuildContainerContent' ).querySelectorAll('input[type="checkbox"]:checked' ); console .log('oCheckeds =>' , oCheckeds); if (!oCheckeds.length) { alert('请选择要打包的 html 文件' ); return ; } let fileConf = []; for (var i = 0 , len = oCheckeds.length; i < len; ++i) { fileConf.push(oCheckeds[i].value); } console .log('fileConf =>' , fileConf.join(',' )); executeBuild({ processingBarId: 'btnBuildProgressHTML' , processingName: 'HTML文件自定义构建' , command: 'gulp' , args: [ 'customBuildHtml' , '--fileConf=' + fileConf.join(',' ) ] }); }; $E('btnBuildDeployHTML' ).onclick = function ( ) { var oCheckeds = $E('htmlBuildContainerContent' ).querySelectorAll('input[type="checkbox"]:checked' ); console .log('oCheckeds =>' , oCheckeds); if (!oCheckeds.length) { alert('请选择要打包的 html 文件' ); return ; } let fileConf = []; for (var i = 0 , len = oCheckeds.length; i < len; ++i) { fileConf.push(oCheckeds[i].value); } console .log('fileConf =>' , fileConf.join(',' )); executeBuild({ processingBarId: 'btnBuildProgressHTML' , processingName: 'HTML文件自定义构建并自动部署' , command: 'gulp' , args: [ 'customBuildAndDeployHtml' , '--fileConf=' + fileConf.join(',' ) ] }); }; gulp.task('customBuildHtml' , [ 'clean' ], function ( ) { console .log(`yargv.fileConf => ${yargv.fileConf} ` ); var fileConf = yargv.fileConf; fileConf = fileConf.replace(/\/html\/\w+\//g , '/html/**/' ); return gulp.src(fileConf.split(',' )) .pipe(debug({title : 'htmlmin file --> ' })) .pipe(htmlmin()) .pipe(debug({title : 'minify-inline file --> ' })) .pipe(minifyInline()) .pipe(gulp.dest(resourceDir)); }); gulp.task('customBuildAndDeployHtml' , [ 'customBuildHtml' ], function ( ) { let conn = ftp.create( { host: '此处是 host ip' , user: 'root' , password: '此处是密码' , parallel: 10 , log: gutil.log } ); gutil.log("----------**********-----------" ); gutil.log(gutil.colors.magenta("---------上传静态资源文件--------" )); gutil.log("----------**********-----------" ); return gulp.src( path.join( resourceDir, "**/*" ), { buffer : false } ) .pipe( conn.dest('/home/richmail/nginx/htdocs/html5' ) ); });
对于 HTML 自定义构建,在 gulpfile.js 里面新加了 customBuildHtml、customBuildAndDeployHtml 两个 task。
总结
工具可以随意切换139邮箱代码当前工作目录,全网(release)、灰度(beta)、测试线(trunk),甚至可以手动输入分支(branch)目录;
工具执行任务是单线程,当前执行任务最多只有 1 个;即,如果当前正在构建时,点击其他按钮是无效的,应该等待当前任务执行完毕之后,才去点击执行其他任务。当然,你也可以右键终止当前任务;
由于工具是基于 Electron 的,所以包比较大,这个你懂的。
参考
Electron 官网:http://electron.atom.io/
gulp 中文网:http://www.gulpjs.com.cn/
Q.js Github项目地址:https://github.com/itorr/q.js