UNPKG

ngui-tools

Version:

A GUI typesetting display engine and cross platform GUI application development framework based on NodeJS/OpenGL

738 lines (619 loc) 22.2 kB
/* ***** BEGIN LICENSE BLOCK ***** * Distributed under the BSD license: * * Copyright (c) 2015, xuewen.chu * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of xuewen.chu nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL xuewen.chu BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * ***** END LICENSE BLOCK ***** */ var util = require('ngui-stew'); var fs = require('ngui-stew/fs'); var child_process = require('child_process'); var keys = require('ngui-stew/keys'); var path = require('ngui-stew/url'); var Buffer = require('buffer').Buffer; var paths = require('./paths'); var uglify = require('../uglify'); var { syscall } = require('ngui-stew/syscall'); var base64_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'.split(''); function exec_cmd(cmd) { var r = child_process.spawnSync('sh', ['-c', cmd]); if (r.status != 0) { if (r.stdout.length) { console.log(r.stdout.toString('utf8')); } if (r.stderr.length) { console.error(r.stderr.toString('utf8')); } process.exit(0); } else { var rv = []; if (r.stdout.length) { rv.push(r.stdout.toString('utf8')); } if (r.stderr.length) { rv.push(r.stderr.toString('utf8')); } return rv.join('\n'); } } function new_zip(self, cwd, source, target) { console.log('Out ', path.basename(target)); exec_cmd('cd ' + cwd + '; rm -r ' + target + '; zip ' + target + ' ' + source.join(' ')); } function unzip(self, source, target) { exec_cmd('cd ' + target + '; unzip ' + source); } function jsa_shell(source, target) { if ( process.platform == 'darwin' ) { exec_cmd(__dirname + '/../bin/osx/jsa-shell ' + source + ' ' + target); } else if ( process.platform == 'linux' ) { exec_cmd(__dirname + '/../bin/linux/jsa-shell ' + source + ' ' + target); } else { throw new Error('No support system'); } } function parse_json_file(filename) { try { return JSON.parse(fs.readFileSync(filename, 'utf-8')); } catch (err) { err.message = filename + ': ' + err.message; throw err; } } var Hash = util.class('Hash', { m_hash: 5381, update_str: function (input) { var hash = this.m_hash; for (var i = input.length - 1; i > -1; i--) { hash += (hash << 5) + input.charCodeAt(i); } this.m_hash = hash; }, update_buff: function (input) { var hash = this.m_hash; for (var i = input.length - 1; i > -1; i--) { hash += (hash << 5) + input[i]; } this.m_hash = hash; }, update_buff_with_len: function (input, len) { var hash = this.m_hash; for (var i = len - 1; i > -1; i--) { hash += (hash << 5) + input[i]; } this.m_hash = hash; }, digest: function () { var value = this.m_hash & 0x7FFFFFFF; var retValue = ''; do { retValue += base64_chars[value & 0x3F]; } while ( value >>= 6 ); return retValue; }, }); function console_log(self, tag, pathname) { console.log(tag, self.m_cur_pkg_name + '/' + pathname); } // 获取跳过文件列表 // "name" pkg 名称 function get_skip_files(self, pkg_json, name) { var rev = [ ]; if (pkg_json.skip) { if (Array.isArray(pkg_json.skip)) { rev = pkg_json.skip; } else { rev = [ String(pkg_json.skip) ]; } delete pkg_json.skip; } if ( !pkg_json.src ) { rev.push('native'); } rev.push('node_modules'); rev.push('package.keys'); rev.push('versions.json'); var reg = new RegExp('^:?' + name + '$'); self.skip.forEach(function (src) { var ls = src.split('/'); if (reg.test(ls.shift()) && ls.length) { rev.push(ls.join('/')); } }); return rev; } // 获取分离文件列表 function get_detach_files(self, pkg_json, name) { var rev = []; if (pkg_json.detach) { if (Array.isArray(pkg_json.detach)) { rev = pkg_json.detach; } else { rev = [ String(pkg_json.detach) ]; } delete pkg_json.detach; } var reg = new RegExp('^:?' + name + '$'); self.detach.forEach(function (src) { var ls = src.split('/'); if (reg.test(ls.shift()) && ls.length) { rev.push(ls.join('/')); } }); return rev; } function action_pkg(self, pathname, ignore_depe) { return action_pkg1(self, pathname, self.m_target_local, self.m_target_public, 0, ignore_depe); } // build pkg item function action_pkg1(self, pathname, target_local, target_public, ignore_public, ignore_depe) { var source_path = util.resolve(pathname); var name = path.basename(source_path); var target_local_path = target_local + '/' + name; var target_public_path = target_public + '/' + name; // ignore network pkg if ( /^https?:\/\//i.test(source_path) ) { return { absolute_path: source_path, relative_path: absolute_path }; } var out = self.m_output_pkgs[name]; if ( out ) { // Already complete return out; } var pkg_json = parse_json_file(source_path + '/package.json'); util.assert(pkg_json.name && pkg_json.name == name, 'Lib name must be consistent with the folder name, ' + name + ' != ' + pkg_json.name); self.m_output_pkgs[name] = out = { pkg_json: pkg_json }; var skip_install = pkg_json.skip_install && pkg_json.origin; if ( skip_install ) { out.absolute_path = pkg_json.origin; } else { out.absolute_path = '../' + name; } out.relative_path = '../' + name; var source_src = source_path; var target_local_src = target_local_path; var target_public_src = target_public_path; if ( pkg_json.src ) { source_src = util.resolve(source_src, pkg_json.src); target_local_src = util.resolve(target_local_src, pkg_json.src); target_public_src = util.resolve(target_public_src, pkg_json.src); } self.m_cur_pkg_name = name; self.m_cur_pkg_source_src = source_src; self.m_cur_pkg_target_local_src = target_local_src; self.m_cur_pkg_target_public_src= target_public_src; self.m_cur_pkg_json = pkg_json; self.m_cur_pkg_no_syntax_preprocess = !!pkg_json.no_syntax_preprocess; self.m_cur_pkg_ngui_syntax = !!pkg_json.ngui_syntax; self.m_cur_pkg_files = { }; self.m_cur_pkg_pkg_files = { }; self.m_cur_pkg_skip_file = get_skip_files(self, pkg_json, name); self.m_cur_pkg_detach_file = get_detach_files(self, pkg_json, name); if ( pkg_json._build ) { // 已经build过,直接拷贝到目标 copy_pkg(self, pkg_json, source_path); return pkg.local_depe; } if ( self.minify == -1 ) { // 使用package.json定义 // package.json 默认启用 `minify` self.m_cur_pkg_enable_minify = 'minify' in pkg_json ? !!pkg_json.minify : false; } else { self.m_cur_pkg_enable_minify = !!self.minify; } fs.rm_r_sync(target_local_path); fs.rm_r_sync(target_public_path); fs.mkdir_p_sync(target_local_path); if ( !ignore_public ) { fs.mkdir_p_sync(target_public_path); } // each dir action_each_pkg_dir(self, ''); var hash = new Hash(); for (var i in self.m_cur_pkg_files) { // 计算 version code hash.update_str(self.m_cur_pkg_files[i]); } pkg_json.version_code = hash.digest(); pkg_json.build_time = new Date().valueOf(); pkg_json._build = true; delete pkg_json.versions; var cur_pkg_files = self.m_cur_pkg_files; var cur_pkg_pkg_files = self.m_cur_pkg_pkg_files; var local_depe = {}; var public_depe = {}; if ( !ignore_depe ) { // depe function solve_external_depe(pathname) { var paths = action_pkg(self, util.isAbsolute(pathname) ? pathname : source_path + '/' + pathname); local_depe[paths.absolute_path] = ''; public_depe[paths.relative_path] = ''; } if (Array.isArray(pkg_json.external_deps)) { pkg_json.external_deps.forEach(solve_external_depe); } else { for ( var i in pkg_json.external_deps ) { solve_external_depe(i); } } // // TODO depe native .. // } pkg_json.external_deps = local_depe; if ( ignore_public ) { var versions = { versions: cur_pkg_files }; fs.writeFileSync(target_local_path + '/versions.json', JSON.stringify(versions, null, 2)); fs.writeFileSync(target_local_path + '/package.json', JSON.stringify(pkg_json, null, 2)); } else { var versions = { versions: cur_pkg_files, pkg_files: cur_pkg_pkg_files }; pkg_json.external_deps = public_depe; fs.writeFileSync(target_local_src + '/versions.json', JSON.stringify(versions, null, 2)); fs.writeFileSync(target_local_src + '/package.json', JSON.stringify(pkg_json, null, 2)); var pkg_files = ['package.json', 'versions.json']; for ( var i in cur_pkg_pkg_files ) { pkg_files.push('"' + i + '"'); } new_zip(self, target_local_src, pkg_files, target_public_path + '/' + name + '.pkg'); delete versions.pkg_files; pkg_json.external_deps = local_depe; fs.rm_sync(target_local_src + '/versions.json'); fs.rm_sync(target_local_src + '/package.json'); fs.writeFileSync(target_local_path + '/versions.json', JSON.stringify(versions, null, 2)); fs.writeFileSync(target_local_path + '/package.json', JSON.stringify(pkg_json, null, 2)); pkg_json.external_deps = public_depe; fs.writeFileSync(target_public_path + '/package.json', JSON.stringify(pkg_json, null, 2)); if ( skip_install ) { var skip_install = util.resolve(target_local_path, '../../skip_install'); fs.mkdir_p_sync(skip_install); fs.rm_r_sync(skip_install + '/' + name); fs.renameSync(target_local_path, skip_install + '/' + name); } } return out; } function copy_file(self, source, target) { fs.mkdir_p_sync( path.dirname(target) ); // 先创建目录 var rfd = fs.openSync(source, 'r'); var wfd = fs.openSync(target, 'w'); var size = 1024 * 100; // 100 kb var buff = new Buffer(size); var len = 0; var hash = new Hash(); do { len = fs.readSync(rfd, buff, 0, size, null); fs.writeSync(wfd, buff, 0, len, null); hash.update_buff_with_len(buff, len); // 更新hash } while (len == size); fs.closeSync(rfd); fs.closeSync(wfd); return { hash : hash.digest() }; } function read_file_text(self, pathname) { var buff = fs.readFileSync(pathname); var hash = new Hash(); hash.update_buff(buff); return { value: buff.toString('utf-8'), hash: hash.digest(), }; } function action_build_file(self, pathname) { // 跳过文件 for (var i = 0; i < self.m_cur_pkg_skip_file.length; i++) { var name = self.m_cur_pkg_skip_file[i]; if ( pathname.indexOf(name) == 0 ) { // 跳过这个文件 return; } } var source = util.resolve(self.m_cur_pkg_source_src, pathname); var target_local = util.resolve(self.m_cur_pkg_target_local_src, pathname); var target_public = util.resolve(self.m_cur_pkg_target_public_src, pathname); var extname = path.extname(pathname).toLowerCase(); var data = null; var is_detach = false; for (var i = 0; i < self.m_cur_pkg_detach_file.length; i++) { var name = self.m_cur_pkg_detach_file[i]; if (pathname.indexOf(name) == 0) { is_detach = true; // 分离这个文件 break; } } switch (extname) { case '.js': case '.jsx': console_log(self, 'Out ', pathname); if ( self.m_cur_pkg_no_syntax_preprocess || (extname == '.js' && !self.m_cur_pkg_ngui_syntax)) { // 不进行jsa转换,直接使用原始代码 data = read_file_text(self, source); } else { jsa_shell(source, source + 'c'); data = read_file_text(self, source + 'c'); fs.rm_sync(source + 'c'); if ( self.m_cur_pkg_enable_minify ) { var minify = uglify.minify(data.value, { toplevel: true, keep_fnames: false, mangle: { toplevel: true, reserved: [ '$' ], keep_classnames: true, }, output: { ascii_only: true } }); if ( minify.error ) { var err = minify.error; err = new SyntaxError( `${err.message}\n` + `line: ${err.line}, col: ${err.col}\n` + `filename: ${source}` ); throw err; } data.value = minify.code; } } fs.mkdir_p_sync( path.dirname(target_local) ); // 先创建目录 fs.writeFileSync(target_local, data.value, 'utf8'); break; case '.keys': console_log(self, 'Out ', pathname); data = read_file_text(self, source); var keys_data = null; try { keys_data = keys.parse(data.value); } catch(err) { console.error('Parse keys file error: ' + source); throw err; } fs.mkdir_p_sync( path.dirname(target_local) ); // 先创建目录 fs.writeFileSync(target_local, keys.stringify(keys_data), 'utf8'); break; default: console_log(self, 'Copy', pathname); data = copy_file(self, source, target_local); break; } self.m_cur_pkg_files[pathname] = data.hash; // 记录文件 hash if ( is_detach ) { fs.cp_sync(target_local, target_public); } else { // add to .pkg public self.m_cur_pkg_pkg_files[pathname] = data.hash; } } function action_each_pkg_dir(self, pathname) { var path2 = util.resolve(self.m_cur_pkg_source_src, pathname); var ls = fs.ls_sync(path2); for (var i = 0; i < ls.length; i++) { var stat = ls[i]; if (stat.name[0] != '.' || !self.ignore_hide) { var path3 = pathname ? pathname + '/' + stat.name : stat.name; if ( stat.isFile() ) { action_build_file(self, path3); } else if ( stat.isDirectory() ) { action_each_pkg_dir(self, path3); } } } } function copy(self, source, target) { fs.cp_sync(source, target, { ignore_hide: self.ignore_hide }); } function copy_pkg(self, pkg_json, source) { util.assert(pkg_json._build, 'Error'); var name = pkg_json.name; var target_local_path = self.m_target_local + '/' + name; var target_public_path = self.m_target_public + '/' + name; var pkg_path = source + '/' + name + '.pkg'; // copy to ramote copy(source, target_public_path); // copy to local copy(source, target_local_path); if ( fs.existsSync(pkg_path) ) { // 有 .pkg // unzip .pkg fs.mkdir_p_sync(self.m_cur_pkg_target_local_src); unzip(self, pkg_path, self.m_cur_pkg_target_local_src); if ( self.m_cur_pkg_target_local_src != target_local_path ) { // src fs.fs.renameSync(self.m_cur_pkg_target_local_src + '/versions.json', target_local_path + '/versions.json'); fs.rm_sync(self.m_cur_pkg_target_local_src + '/package.json'); } fs.rm_sync(pkg_path); } else { // 没有.pkg文件 new_zip(self, target_public_path, 'package.json versions.json', name + '.pkg'); fs.rm_sync(target_public_path + '/versions.json'); } } // 拷贝外部文件 function copy_outer_file(self, items) { for (var source in items) { var target = items[source] || source; console.log('Copy', source); fs.cp_sync(self.m_source + '/' + source, self.m_target_local + '/' + target, { ignore_hide: self.ignore_hide }); } } function action_result(self) { var result = { }; var ok = 0; for ( var name in self.m_output_pkgs ) { result[name] = self.m_output_pkgs[name].pkg_json; ok = 1; } if ( ok ) { fs.writeFileSync(self.m_target_public + '/packages.json', JSON.stringify(result, null, 2)); } else { console.log('No package build'); } } /** * @class NGUIBuild */ var NGUIBuild = util.class('NGUIBuild', { m_source : '', m_target_local : '', m_target_public : '', m_cur_pkg_name : '', m_cur_pkg_source_src : '', m_cur_pkg_target_local_src : '', m_cur_pkg_target_public_src : '', m_cur_pkg_json : null, m_cur_pkg_no_syntax_preprocess : false, m_cur_pkg_ngui_syntax : false, m_cur_pkg_files : null, m_cur_pkg_pkg_files : null, m_cur_pkg_detach_file : null, m_cur_pkg_skip_file : null, m_cur_pkg_enable_minify : false, m_output_pkgs : null, // public: ignore_hide: true, // 忽略隐藏文件 minify: -1, // 缩小与混淆js代码,-1表示使用pkg.keys定义 skip: null,// 跳过文件列表 detach: null, // 分离文件列表 /** * @constructor */ constructor: function (source, target) { var self = this; this.skip = []; this.detach = []; this.m_output_pkgs = {}; this.m_source = util.resolve(source); this.m_target_local = util.resolve(target, 'install'); this.m_target_public = util.resolve(target, 'public'); util.assert(fs.existsSync(this.m_source), 'Build source does not exist ,{0}', this.m_source); }, /** * action */ build: function() { var self = this; var keys_path = self.m_source + '/app.keys'; fs.mkdir_p_sync(this.m_target_local); fs.mkdir_p_sync(this.m_target_public); if ( !fs.existsSync(keys_path) ) { // No exists app.keys file // build pkgs // scan each current target directory fs.ls_sync(self.m_source).forEach(function(stat) { if ( stat.name[0] != '.' && stat.isDirectory() && fs.existsSync( self.m_source + '/' + stat.name + '/package.json' ) ) { action_pkg(self, self.m_source + '/' + stat.name); } }); action_result(self); return; } var keys_object = keys.parseFile( keys_path ); var apps = []; for (var name in keys_object) { if (name[0] != '@') { apps.push(name); } else if (name == '@copy') { copy_outer_file(self, keys_object[name]); } } // npm install console.log(`Install dependencies ...`); syscall(`npm install ${apps.join(' ')} ngui`); apps.forEach(e=>fs.unlinkSync('node_modules/' + e)); fs.rm_r_sync('package-lock.json'); // build application pkgs var pkgs_path = self.m_source + '/node_modules'; if ( fs.existsSync(pkgs_path) && fs.statSync(pkgs_path).isDirectory() ) { var target_local = this.m_target_local; // + '/pkgs'; fs.mkdir_p_sync(target_local); fs.ls_sync(pkgs_path).forEach(function(stat) { var source = pkgs_path + '/' + stat.name; if ( stat.isDirectory() && fs.existsSync(source + '/package.json') ) { action_pkg1(self, source, target_local, self.m_target_public, false, true); } }); } // build apps apps.forEach(e=>action_pkg(self, self.m_source + '/' + e)); action_result(self); }, /** * @func initialize() init project directory and add examples */ initialize: function() { var project_name = path.basename(process.cwd()) || 'nguiproj'; var app_keys = this.m_source + '/app.keys'; var app = { '@project_name': project_name, }; var default_modules = paths.default_modules; if ( default_modules && default_modules.length ) { var pkgs_dirname = this.m_source + '/node_modules'; fs.mkdir_p_sync(pkgs_dirname); // create pkgs dir // copy default pkgs default_modules.forEach(function(pkg) { var pathname = pkgs_dirname + '/' + path.basename(pkg); if ( !fs.existsSync(pathname) ) { // if no exists then copy fs.cp_sync(pkg, pathname); // copy pkgs } }); } if (fs.existsSync(app_keys)) { // 如果当前目录存在app.keys文件附加到当前 app = util.assign(app, keys.parseFile(app_keys)); } else { if (!fs.existsSync(project_name)) { var json = { name: project_name, app_name: project_name, id: `org.ngui.${project_name}`, main: 'index.js', version: '1.0.0', ngui_syntax: true, }; fs.mkdir(project_name); fs.writeFileSync(project_name + '/package.json', JSON.stringify(json, null, 2)); fs.writeFileSync(project_name + '/index.jsx', ` import { GUIApplication, Root, Indep } from 'ngui'; new GUIApplication().start( <Root> <Indep align="center">Hello world</Indep> </Root> ); `); } if (!fs.existsSync('examples')) { // copy examples pkg fs.cp_sync(paths.examples, this.m_source + '/examples'); } app['examples'] = ''; app[project_name] = ''; } // write new app.keys fs.writeFileSync(app_keys, keys.stringify(app)); }, }); exports.NGUIBuild = NGUIBuild;