美文网首页
可视化构建工具小结(1-5)

可视化构建工具小结(1-5)

作者: Gopal | 来源:发表于2018-02-23 11:48 被阅读361次

    项目简介
    为了更加快速便捷的开发前端,将一些常见的功能进行可视化

    项目结构

    项目结构

    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地址
    使用的话,不多说。

    electron视频教程
    electron官方文档

    重点:

    重点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中类的相关知识

    项目结构

    项目中读取和修改文件

    /**
     * @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:工具类的封装
    场景:在项目中多处使用到了提醒和存储功能
    目的:为了便于管理和方便使用
    (其实也是参考了文档系统的相关配置)

    utils文件夹

    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()
      路径的拼接


    • electron一些常用模块

    • import和require,还有require.js

    • 各个库的版本以及怎么解决?
      在这个项目中,各个信息如下

    • Notifier思想的学习

    • sh文件以及bat文件

    • 端口被占用,如何通过命令行杀死

    • 读取以及修改yaml文件,json文件

    • node-pty的作用??怎么使用???使用中环境的配置???

    • 项目注释(JSDoc的使用)

    • 多重if如何优化?

    相关文章

      网友评论

          本文标题:可视化构建工具小结(1-5)

          本文链接:https://www.haomeiwen.com/subject/grkynxtx.html