可视化构建工具小结(1-5)
项目简介
为了更加快速便捷的开发前端,将一些常见的功能进行可视化
项目结构
![](https://img.haomeiwen.com/i1784460/62187702c25e38c5.png)
Electron入门
首先是Electron的学习,项目中使用到了electron-vue。
electron-vue
基于vue来构造electron应用程序的样板代码
主要是为了避免使用vue手动建立起electron应用程序 。electron-vue充分利用了 vue-cli作为脚手架,加上拥有 vue-loader的webpack、可以使用electron-builder或者electron-packager打包,以及一些常见的插件,比如vue-router、vuex等
electron-vue的GitHub地址
使用的话,不多说。
重点:
重点1:electron通信
通过IPC(进程间通信)模块允许你在主进程和渲染进程之间发送和接受同步和异步消息
要清楚知道主进程和渲染进程的关系,而且渲染进程中可以通过.remote调用主进程中的模块,但是这样会造成性能损耗???(具体还是没有弄清楚)
const BrowserWindow = require('electron').remote.BrowserWindow
- 异步消息
使用ipc以异步方式在进程之间发送消息是首选的方法,因为它会在完成的时候返回,而不会阻止同一个进程中的操作
渲染进程:
const ipc = require('electron').ipcRenderer
const asyncMsgBtn = document.getElementById('async-msg')
asyncMsgBtn.addEventListener('click', function () {
ipc.send('asynchronous-message', 'ping')
})
ipc.on('asynchronous-reply', function (event, arg) {
const message = `异步消息回复: ${arg}`
document.getElementById('async-reply').innerHTML = message
})
主进程:
const ipc = require('electron').ipcMain
ipc.on('asynchronous-message', function (event, arg) {
event.sender.send('asynchronous-reply', 'pong')
})
- 同步消息
可以使用ipc模块在进程之间发送同步消息,但是需要注意,这个方法的同步特性意味着它在完成任务的时候会阻止其他操作
渲染进程:
const ipc = require('electron').ipcRenderer
const syncMsgBtn = document.getElementById('sync-msg')
syncMsgBtn.addEventListener('click', function () {
const reply = ipc.sendSync('synchronous-message', 'ping')
const message = `同步消息回复: ${reply}`
document.getElementById('sync-reply').innerHTML = message
})
主进程:
const ipc = require('electron').ipcMain
ipc.on('synchronous-message', function (event, arg) {
event.returnValue = 'pong'
})
- 与隐藏窗口的通信
通常的做法是创建一个不可见的新的窗口(记得这个是渲染器进程),以便在不影响主进程窗口中的性能的前提下运行任务。
const BrowserWindow = require('electron').remote.BrowserWindow
const ipcRenderer = require('electron').ipcRenderer
const path = require('path')
const invisMsgBtn = document.getElementById('invis-msg')
const invisReply = document.getElementById('invis-reply')
invisMsgBtn.addEventListener('click', function (clickEvent) {
const windowID = BrowserWindow.getFocusedWindow().id
const invisPath = 'file://' + path.join(__dirname, '../../sections/communication/invisible.html')
// 创建一个新的窗口
let win = new BrowserWindow({ width: 400, height: 400, show: false })
win.loadURL(invisPath)
// 当导航完成时候发出事件,onload事件也要完成
win.webContents.on('did-finish-load', function () {
const input = 100
// 不同的发送方式,第一个是事件名称,第二个是参数,第三个是窗口的ID
// 通过channel发送异步消息给渲染进程,你也可以发送任意参数,渲染进程可以通过ipcRenderer监听channel处理信息。
win.webContents.send('compute-factorial', input, windowID)
})
})
ipcRenderer.on('factorial-computed', function (event, input, output) {
const message = `${input} 的阶乘是 ${output}`
invisReply.textContent = message
})
隐藏窗口页面的HTML:
<html>
<script type="text/javascript">
const ipc = require('electron').ipcRenderer
const BrowserWindow = require('electron').remote.BrowserWindow
ipc.on('compute-factorial', function (event, number, fromWindowId) {
const result = factorial(number)
// 根据ID查找窗口
const fromWindow = BrowserWindow.fromId(fromWindowId)
fromWindow.webContents.send('factorial-computed', number, result)
window.close()
})
function factorial (num) {
if (num === 0) return 1
return num * factorial(num - 1)
}
</script>
</html>
重点2:background进程的实现
主要是用到了上面打开不可见窗口的方式实现
疑问点1
const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080`
: `file://${__dirname}/index.html`
const childURL = process.env.NODE_ENV === 'development'
? `http://localhost:9081/background.html`
: `file://${__dirname}/background.html`
为什么这里的路径要这么设置?还有为什么端口要不一样???
// 创建一个不可见的窗口
var winChild = new BrowserWindow({
width: 700,
height: 550,
show: false
})
// 打开调试工具
winChild.openDevTools()
winChild.loadURL(childURL)
winChild.on('closed', () => {
winChild = null
})
上面我们就创建了一个不可见的窗口了,这里的代码放在主进程中执行。
那么,我们主要是在background进程中做哪些操作呢?又怎么进行通信呢??
这里是要使用了大佬封装的Notifier
emmmmm,这个要自己看另一篇笔记。
主要的使用如下:
在background.js中注册模块和方法
/**
* @file: 本文件主要实现background进程的操作
* @description: 在后台进程中实施操作,防止阻塞主进程
* @author: fenggp1
* @copyright: COPYRIGHT (©) 2016-2017 深圳美云智数科技有限公司
* @Created Time: 2017-12-27 16:44:51
* @Last Modified By: fenggp1
* @Last Modified Time: 2017-12-27 16:44:51
*/
import { ipcRenderer } from 'electron'
import bg from './lib/bg'
import Notifier from '../main/notifier/index.js'
import { resolve } from 'dns';
const rq = require('electron-require')
const path = require('path')
const remote = require('electron').remote
const dialog = remote.dialog
const ipcMain = require('electron').ipcMain
const BrowserWindow = require('electron').BrowserWindow
var shell = require('shelljs')
var child_process = require('child_process')
var exce = require('child_process').exec
// 通过registerModule注册一个模块,将模块的winId和模块暴露的方法记录在主进程
Notifier.getInstance().registerModule('ProjectManage', {
a: () => {
console.log('Gpout')
return Promise.resolve(1)
},
/**
* @description 选择一个项目
* @returns {string} 项目的路径
*/
selectProject: () => {
console.log('replySel')
return new Promise((resolve, reject) => {
dialog.showOpenDialog({
properties: ['openFile', 'openDirectory']
}, (files) => {
if (files) {
console.log(files)
resolve(files)
}
})
})
},
/**
* @description 新建一个项目
* @param {object} form 文件的相关配置
* @return {str} 创建的结果
*/
createProject: (form) => {
var projectName = form.settings.tplConfig.name
console.log('项目名称:' + projectName)
var workspace = form.workspace
console.log('工作空间:' + workspace)
var localMxCli = process.env.NODE_ENV === 'development' ? path.join(process.cwd(), './src/mx-cli/') : path.join(app.getAppPath(), '/../../MXCLI')
// var localMxCli = process.env.NODE_ENV === 'development' ? path.join(process.cwd(), './src/mx-cli/') : path.join(app.getAppPath(), './mx-cli')
console.log(localMxCli, "--------------------------")
rq.set('local', localMxCli);
var localMxFramework = process.env.NODE_ENV === 'development' ? path.join(process.cwd(), './src/templates/mx-framework/') : path.join(app.getAppPath(), '/../../Templates/MXFramework')
// var localMxFramework = process.env.NODE_ENV === 'development' ? path.join(process.cwd(), './src/templates/mx-framework/') : path.join(app.getAppPath(), './templates/mx-framework')
var mxInit = rq.local( 'index.js')
// form = require('/Users/zhongjw/work/h5-res-mx-visual-builder/src/templates/mx-framework/meta.js')
return new Promise((resolve, reject) => {
mxInit(localMxFramework, path.join(workspace, projectName), form, function(result){
resolve(result)
})
})
}
})
疑问点2:如果很多模块的时候要怎么设置呢??全部放在background.js中?会不会很“臃肿”
然后在主进程index.js中初始化一下
import MainNotifier from './notifier/mainNotifier.js'
// Notifier初始化
MainNotifier.getInstance()
最后在渲染进程(其实background进程也是渲染进程)中调用模块和方法
Notifier.getInstance().resolveModule('ProjectManage').selectProject().then(filePath => {
self.filepath = filePath[0]
self.form.workspace = filePath[0]
})
重点3:类的使用
在主进程中需要进行一些操作,或者对页面的一些基础设置,如果全部将这些写在主进程中,显得不直观,而且很“臃肿”,这里使用了类进行管理
项目结构
主要使用了ES6中类的相关知识
![](https://img.haomeiwen.com/i1784460/d9fba3c357d4d594.png)
项目中读取和修改文件
/**
* @file: 对文件进行管理操作
* @description: 读取文件、修改文件等
* @author: fenggp1
* @copyright: COPYRIGHT (©) 2016-2017 深圳美云智数科技有限公司
* @Created Time: 2017-12-27 17:12:14
* @Last Modified By: fenggp1
* @Last Modified Time: 2017-12-27 17:12:14
*/
var path = require('path')
var fsUtils = require('fs-utils')
var fs = require('fs')
/**
* @module ./lib/file-manage
* @default 'fileManage'
* @constant {class} 文件管理类
* @description 管理文件的类
* @export
* @class FileManage
*/
export default class FileManage {
/**
* @class
* @classdesc 这里是一个构造函数
* @memberof FileManage
*/
constructor () {
}
/**
* @description 将修改内容写入目标yaml文件
* @param {string} dest 目标文件的路径
* @param {object} str 写入的内容
* @param {object} opts 配置
* @access public
* @memberof FileManage
*/
writeFileYAML (dest, str, opts) {
fsUtils.writeYAMLSync(dest, str, opts)
}
/**
* @description 读取yaml文件的内容
* @access public
* @param {string} filepath 目标文件的路径
* @returns {object} 返回读取yaml的结果
* @memberof FileManage
*/
readFileYAML (filepath) {
return fsUtils.readYAMLSync(filepath)
}
}
在主进程中使用
import FileManage from './lib/file-manager'
var fileManage = new FileManage()
fileManage.writeFileYAML(targetFile.fileTotalPath, meta, {encoding: 'utf8'})
var readYamlResult = fileManage.readFileYAML(filePath)
重点4:模拟终端
这个路途有点曲折,首先开始用的是node.js中的exec方法或者说shell.js
但是将一个进程跑起来之后怎么杀死成为一个问题,也从网上找了一个解决方法,但是觉得还是不够严谨
下面是开始执行命令的方法和结束(将进程kill掉),但是这样只是解决了进程方面的问题,界面的话,还是没有得到解决(而且不够好)
Notifier.getInstance().registerModule('LocalDebug', {
startRun: (currentProject) => {
shell.cd(currentProject.path)
this.childProcess = child_process.exec('npm run dev', (error, stdout, stderr) => {
console.log('Gperror: '+ error + 'Gpstdout: ' + stdout + 'Gpstderr: ' + stderr)
})
return new Promise((resolve, reject) => {
this.childProcess.stdout.on('data', (result) => {
console.log('dataGp:' + result)
resolve(result)
})
})
},
stopRun: () => {
console.log(process.platform)
var cmd = process.platform == 'win32'?'netstat -ano':'ps aux'
var exec = require('child_process').exec
var port = '8019'
exec(cmd, function(err, stdout, stderr) {
if (err) {
return console.log(err)
}
stdout.split('\n').filter(function(line) {
var p = line.trim().split(/\s+/)
var address = p[1]
console.log(address)
if (address != undefined) {
if (address.split(':')[3] === port) {
if (process.platform === 'win32') {
exec('taskkill /F /pid ' + p[4], function(err, stdout, stderr) {
if(err) {
resolve('释放指定端口失败!!')
}
resolve('占用指定端口的程序被成功杀掉!')
})
} else {
exec('kill -9 ' + p[4], function(err, stdout, stderr) {
if(err) {
return console.log('释放指定端口失败!!')
}
console.log('占用指定端口的程序被成功杀掉!')
})
}
}
}
})
})
}
})
最后的解决方法:使用node-pty以及xterm.js相结合
node-pty的GitHub地址
xterm.js
xterm.js文档
node-pty的一个简单例子
var os = require('os');
var pty = require('node-pty');
var shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
var ptyProcess = pty.spawn(shell, [], {
name: 'xterm-color',
cols: 80,
rows: 30,
cwd: process.env.HOME,
env: process.env
});
ptyProcess.on('data', function(data) {
console.log(data);
});
ptyProcess.write('ls\r');
ptyProcess.resize(100, 40);
ptyProcess.write('ls\r');
下面是xterm.js的一个简单例子:
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="node_modules/xterm/dist/xterm.css" />
<script src="node_modules/xterm/dist/xterm.js"></script>
</head>
<body>
<div id="terminal"></div>
<script>
var term = new Terminal();
term.open(document.getElementById('terminal'));
term.write('Hello from \033[1;3;31mxterm.js\033[0m $ ')
</script>
</body>
</html>
上面的例子可以看出,我们可以通过node-pty执行命令相关操作,然后通过xterm.js显示界面,下面是一个两者结合使用的例子
demo地址
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>node-pty Electron example</title>
<link rel="Stylesheet" href="./node_modules/xterm/lib/xterm.css">
</head>
<body>
<div id="xterm"></div>
</body>
<script>
require('./renderer.js')
</script>
</html>
var os = require('os');
var pty = require('node-pty');
var Terminal = require('xterm');
// Initialize node-pty with an appropriate shell
const shell = process.env[os.platform() === 'win32' ? 'COMSPEC' : 'SHELL'];
const ptyProcess = pty.spawn(shell, [], {
name: 'xterm-color',
cols: 80,
rows: 30,
cwd: process.cwd(),
env: process.env
});
// Initialize xterm.js and attach it to the DOM
const xterm = new Terminal();
xterm.open(document.getElementById('xterm'));
// Setup communication between xterm.js and node-pty
xterm.on('data', (data) => {
ptyProcess.write(data);
});
ptyProcess.on('data', function (data) {
xterm.write(data);
});
项目中的实现基本借鉴于这个demo
重点5:命令行的了解
因为涉及到命令行的相关操作,而自己这方面不是很熟悉,理解一些问题或者遇到一些困难的时候找不到突破口,这其实也谈不上重点,只是应该引起自己足够的重视吧。
比如,不同操作系统之间的一些命令行的区别等等。
重点6:工具类的封装
场景:在项目中多处使用到了提醒和存储功能
目的:为了便于管理和方便使用
(其实也是参考了文档系统的相关配置)
![](https://img.haomeiwen.com/i1784460/942325a5a7b70dad.png)
localStorage的封装
/**
* @file: 实现本地储存功能
* @description: 封装本地储存功能
* @author: fenggp1
* @copyright: COPYRIGHT (©) 2016-2017 深圳美云智数科技有限公司
* @Created Time: 2017-12-28 10:18:29
* @Last Modified By: fenggp1
* @Last Modified Time: 2017-12-28 10:18:29
*/
class WebStoarge {
constructor () {
if (!window.localStorage) {
console.warn('该浏览器不支持window.localStorage')
}
this.storage = {}
}
injection (name) {
if (name && typeof name === 'string') {
this.storage[name] = 'BUILDER-' + name.toUpperCase()
}
}
get (name) {
if (name in this.storage) {
return window.localStorage.getItem(this.storage[name])
}
return ''
}
set (name, value) {
if (name in this.storage) {
window.localStorage.setItem(this.storage[name], value)
}
}
remove (name) {
if (name in this.storage) {
window.localStorage.removeItem(this.storage[name])
}
}
clear () {
window.localStorage.clear()
}
}
export default new WebStoarge()
提示tip的封装:
/**
* @file: 实现提示的功能
* @description: 使用element-UI的提示,可以根据不同的类型和提示语进行提示
* @author: fenggp1
* @copyright: COPYRIGHT (©) 2016-2017 深圳美云智数科技有限公司
* @Created Time: 2017-12-28 10:13:10
* @Last Modified By: fenggp1
* @Last Modified Time: 2017-12-28 10:13:10
*/
/**
* @description 提示语工具函数
*
* @param {string} type
* @param {string} msg
*/
function tip(type, msg) {
this.$message({
type: type,
message: msg
})
}
export default tip
然后在main.js中
Vue.prototype.$tip = tip
Vue.prototype.$storage = webStorage
这样就可以在项目中愉快的使用了
this.$tip('success', result)
疑问3:localStorage的代替方案????
重点7:Promise的使用以及对象的深浅复制
其他方面
-
工作路径
process.cwd()和__dirname和path.join()
路径的拼接
![](https://img.haomeiwen.com/i1784460/a44a899c873db823.png)
-
electron一些常用模块
-
import和require,还有require.js
-
各个库的版本以及怎么解决?
在这个项目中,各个信息如下
![](https://img.haomeiwen.com/i1784460/35be373d2fc64be6.png)
-
Notifier思想的学习
-
sh文件以及bat文件
-
端口被占用,如何通过命令行杀死
-
读取以及修改yaml文件,json文件
-
node-pty的作用??怎么使用???使用中环境的配置???
-
项目注释(JSDoc的使用)
-
多重if如何优化?