下载和安装较新的Nodejs稳定版

网页
https://nodejs.org/en/about/releases/
列出了会持续修复bug的nodejs发行版本,
撰写本文时间为2019年11月,所以我这儿选择了下载nodejs 发行版v12的最新版本,
https://nodejs.org/download/release/latest-v12.x/
由于我当前使用的是window64位系统,所以下载了 windows 64位安装包,并安装。
(如果你已知知道在后续工作中将要使用的js库与nodejs v12不兼容,你也可以选择v10或v8等低版本,
https://nodejs.org/download/release/latest-v10.x/)
在安装nodejs之前,注意关闭一切可能使用nodejs的程序(或许需要重启系统后关闭所有系统托盘的程序),保证没有程序在使用nodejs后再安装。

更新electron工程的依赖

根据我的经验,在一个依赖库很多的个旧工程上更新部分库是是一个比较有风险的举措,因为一些许多年前的旧库可能与其他新的库有冲突,而这些冲突导致的错误又是很难排查的。
我的建议是,删除工程下整个node_modules目录,并且将package.json中的所有依赖的声明删除,重新安装这些依赖的最新版本。
事实上在本次实践中,我不光要更新依赖库,重新整理工程的目录结构,重新准备构建脚本。

新的工程结构和构建脚本

参考
https://github.com/SimulatedGREG/electron-vue
假设我原有的工程名为 ZPub-client,那么我在另外一个文件夹中执行以下命令得到一个新的electron+vue开发工程。

npm install -g vue-cli
vue init simulatedgreg/electron-vue ZPub-client

这样我们基于electron-vue生成了一个名为ZPub-client的工程。

新的依赖库

更新依赖到最新版

注意,由于electron-vue 中的依赖也比较旧,我们需要使用更新的依赖,复制 package.json里的dependencies配置,改写为如下的js片断,然后在js控制台执行。

'yarn add '+Object.keys({
    "vue": "^2.5.16",
    "axios": "^0.18.0",
    "vue-router": "^3.0.1"
  }).join(' ')

返回的字符串即为,一条依赖库安装命令

yarn add vue axios vue-router

同样的方法得到 devDependencies 部分的依赖的安装命令

yarn add ajv babel-core babel-loader babel-plugin-transform-runtime babel-preset-env babel-preset-stage-0 babel-register babili-webpack-plugin cfonts chalk copy-webpack-plugin cross-env css-loader del devtron electron electron-debug electron-devtools-installer electron-packager electron-rebuild babel-eslint eslint eslint-friendly-formatter eslint-loader eslint-plugin-html eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-standard mini-css-extract-plugin file-loader html-webpack-plugin multispinner node-loader style-loader url-loader vue-html-loader vue-loader vue-style-loader vue-template-compiler webpack-cli webpack webpack-dev-server webpack-hot-middleware webpack-merge -D

删除package.json里的dependencies配置和devDependencies配置,再在windows命令行里执行上面两条安装依赖的命令。
注意安装完依赖后的警告,
eslint-plugin-vue提示要使用eslint5.x
将eslint降到5.x之后,又触发了eslint-config-standard的警告,又将
eslint-config-standard降到12.x

去掉与最新版babel不兼容的库,安装新库

安装完新的依赖,有些库可能长时间没有维护了,可能存在兼容性问题,所以需要尝试编译一下。

yarn run pack

会发现有很多错误,问题仍然是一些库的版本过旧,并且库的名字也更换了,根据这些错误安装了新的版本。依次有

yarn remove babel-core babel-plugin-transform-runtime babel-preset-env babel-preset-stage-0 
yarn add @babel/core @babel/preset-env -D

调整babel的配置文件

然后还有babel配置的错误,调整.babelrc.js文件,以适合babel的最新版本,在此不详述了,只放代码

module.exports = function (api) {
  if (api && api.cache) {
    api.cache(true);
  }
  const babelrc = {
    "comments": false,
    "env": {
      "main": {
        "presets": [
          [
            '@babel/preset-env', {
              targets: {
                "node": 8
              }
            }
          ]
        ]
      },
      "renderer": {
        "presets": [
          [
            '@babel/preset-env', {
              targets: {
                browsers: ['chrome >= 54']
              },
              modules: false,
            }
          ]
        ]
      },
      "web": {
        "presets": [
          [
            '@babel/preset-env', {
              targets: {
                browsers: ['chrome >= 45']
              },
              modules: false,
            }
          ]
        ]
      }
    },
    "plugins": []
  }
  if (process.env.BABEL_ENV) {
    return {
      "presets": babelrc.env[process.env.BABEL_ENV].presets,
      "plugins": babelrc.plugins
    }
  } else {
    throw new Error('没有配置 process.env.BABEL_ENV')
  }
}

修复eslint错误

再执行yarn run pack
这时有一些eslint报错。
第一个错误与vue的识别有关
.eslintrc.js里配置eslintplugins 加上vue相关配置

yarn add eslint-plugin-vue -D  

.eslintrc.js 内容如下,尤其注意 
parser:"babel-eslint"
改为
parserOptions.parser:"babel-eslint"

module.exports = {
  root: true,
  parserOptions: {
    parser: "babel-eslint",
    sourceType: 'module'
  },
  env: {
    browser: true,
    node: true
  },
  extends: [
    "plugin:vue/recommended",
    "standard"
  ],
  globals: {
    __static: true
  },
  plugins: [
    'vue'
  ],
  'rules': {
    // allow paren-less arrow functions
    'arrow-parens': 0,
    // allow async-await
    'generator-star-spacing': 0,
    // allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
  }
}

解决ejs模板编译问题

再次执行 yarn run pack
会发现有一个ejs错误,提示process is not defined
这应该是webpack的新版本与html-webpack-plugin这个老插件之间的兼容问题,在网上搜索找到一个解决方案,改webpack中的html-webpack-plugin插件初始化配置中加入模板参数配置如下

      templateParameters(compilation, assets, options) {
        return {
          compilation: compilation,
          webpack: compilation.getStats().toJson(),
          webpackConfig: compilation.options,
          htmlWebpackPlugin: {
            files: assets,
            options: options
          },
          process,
        };
      }

再次执行yarn run pack
工程终于编译成功了。

页面上报错 process is not defined

行yarn run dev进入开发,
这时窗口内容一片空白,js控制台报错 process is not defined
说明不能调用 nodejs 接口,在网上查了一下原来在electron5.x以上版本,页面中要使用nodejs接口要在 new BrowserWindow 时加上配置``。

  mainWindow = new BrowserWindow({
    height: 563,
    useContentSize: true,
    width: 1000,
    webPreferences: {
      nodeIntegration: true // 加上这一行才能使用nodejs里的接口
    }
  })

终于工程可以正常运行了。

迁移旧代码

比较原有的ZPub-client工程和新的ZPub-client工程的区别,将旧工程里的一些源文件复制到新的工程里来,

添加 .editorconfig 配置文件

添加 .prettierrc.js 配置文件

注意在vscode里安装vetur插件,并在vscode的个人配置里添加,下面这一行,以使用prettier来格式化js

    "vetur.format.defaultFormatter.js": "prettier"

在工程根目录下添加 .prettierrc.js,内容如下,以保证格式化时使用双引号,不加分号

module.exports = {
  "eslintIntegration": true,
  "singleQuote": true,
  "semi": false,
  "eslint.autoFixOnSave": true
}

更新旧工程的依赖库

如同之前进行过的,我们复制 package.json里的dependencies配置和 devDependencies,在js控制台执行一个转换语句,得到要添加的库。

yarn add brace echarts element-ui form-data jschardet lodash.throttle lodash.uniq multer node-rsa node-xlsx oss-uploader.js qiniu remarkable sortablejs vue-async-computed vue-echarts vue-infinite-scroll zip-local
yarn add babel-plugin-syntax-jsx babel-plugin-transform-vue-jsx -D

安装这些依赖

更新.babelrc.js

更新.babelrc.js,添加plugin

        "plugins": ['transform-vue-jsx']

更新.eslintrc.js

参考旧工程使用更宽松一点的校验规则,.eslintrc.js修改如下

module.exports = {
  root: true,
  parserOptions: {
    parser: "babel-eslint",
    sourceType: 'module'
  },
  env: {
    browser: true,
    node: true
  },
  extends: [
    "plugin:vue/essential",
    "standard"
  ],
  globals: {
    __static: "writable",
    $: "writable",
    Vue: "writable",
    axios: "writable"
  },
  plugins: [
    'vue'
  ],
  'rules': {
    "indent": "warn",
    "semi": "warn",
    "comma-dangle": "warn",
    "comma-spacing": "warn",
    "generator-star-spacing": "off",
    "no-caller": "off",
    "no-control-regex": "off",
    "no-extend-native": "off",
    "no-irregular-whitespace": "off",
    "no-mixed-operators": "off",
    "no-new": "off",
    "no-new-func": "off",
    "no-unused-vars": "warn",
    "no-useless-constructor": "warn",
    "new-cap": "warn",
    "key-spacing": "warn",
    "arrow-spacing": "warn",
    "keyword-spacing": "warn",
    "space-infix-ops": "warn",
    "space-before-blocks": "off",
    "space-before-function-paren": "off",
    "spaced-comment": "warn",
    "vue/no-unused-vars": "warn",
    "standard/object-curly-even-spacing": "warn",
    "standard/no-callback-literal": "warn",
    "prefer-const": "warn",
    "prefer-promise-reject-errors": "warn",
    "quotes": "warn",
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
  }
}

更新.eslintignore

由于我们引入了eslint,但一些第三方库功能是正常的,但因为不符合当前工程使用的eslint校验规则,会有很多的报错,需要取消掉,.eslintignore内容如下

node_modules/*
/src/renderer/lib/
/static/

调整文件结构

  • 原来放src下的入口js文件,放到/src/renderer/目录下
  • 原来放在src目录下与主进程无关,与界面进程有关的js移到/src/renderer/目录下
  • 原来放在src目录下与主进程有关,与界面进程无关的js移到/src/main/目录下,例如 boot.js重命名为index.js移到/src/main/目录下
  • index.html、login.html重名为index.ejs、login.ejs 放到/src/目录下
  • 原来放在assets目录下在页面上直接引用不需要进行模块管理的css,移到/static/目录下

调整webpack配置

调整入口文件

因为本工程有多个页面,所以要添加多个入口文件,webpack.renderer.config.jsentry配置修改如下

  entry: {
    renderer: path.join(__dirname, '../src/renderer/main.js'),
    login: path.join(__dirname, '../src/renderer/login.js')
  }
添加新的页面 webpack.renderer.config.js 中再加一个HtmlWebpackPlugin实例
    new HtmlWebpackPlugin({
      filename: 'login.html',
      template: path.resolve(__dirname, '../src/login.ejs'),
      templateParameters(compilation, assets, options) {
        return {
          compilation: compilation,
          webpack: compilation.getStats().toJson(),
          webpackConfig: compilation.options,
          htmlWebpackPlugin: {
            files: assets,
            options: options
          },
          process,
        };
      },
      minify: {
        collapseWhitespace: true,
        removeAttributeQuotes: true,
        removeComments: true
      },
      nodeModules: process.env.NODE_ENV !== 'production'
        ? path.resolve(__dirname, '../node_modules')
        : false
    }),

login.html 改写为 login.ejs

因为使用ejs模板解析,为页面准备了一些必要的上下文件,所以html要改为ejs
login.ejs内容如下

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>泽元协同编纂平台</title>
    <% if (htmlWebpackPlugin.options.nodeModules) { %>
      <!-- Add `node_modules/` to global paths so `require` works properly in development -->
      <script>
        require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>')
      </script>
    <% } %>
  <script>
    window.isBackgroundWindow = true // 登录窗口在登录完成后即后台运行
  </script>

  <link rel="stylesheet" href="./static/styles/bootstrap.min.css">
  <link rel="stylesheet" href="./static/styles/font-awesome.min.css">
  <link rel="stylesheet" href="./static/styles/element-icon-font-awesome.css">
  <link rel="stylesheet" href="./static/styles/element-ui.theme-green.css">
  <link rel="stylesheet" href="./static/assets/main.css">
  <style>
    html,
    body {
      height: 100%;
      width: 100%;
      background-color: #27a16c;
      margin: 0px;
    }
  </style>
  </head>
  <body style="overflow: hidden;border:2px solid #31a66c">
    <div id="app"></div>
    <!-- Set `__static` path to static files in production -->
    <% if (!process.browser) { %>
      <script>
        if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
      </script>
    <% } %>

    <!-- webpack builds are automatically injected -->
  </body>
</html>

index.html 改写为 index.ejs

因为使用ejs模板解析,为页面准备了一些必要的上下文件,所以html要改为ejs
index.ejslogin.ejs完全相同

整理代码

执行

yarn run lint:fix
yarn run lint

有大量的警告和错误,警告先不处理,错误要一一改正,才能编译成功

尝试编译

执行

yarn run pack

在处理完eslint的警告后,尝试编译工程。
这时会有许多编译错误,主要是一些路径错误、库引用错误,还有eslint校验错误。
根据提示一一修正直到可以编译成功。

尝试开发模式

执行

yarn run dev

这时会遇到一引起electron升级后api更改引发的错误,
例如
原来合建单例的方法 makeSingleInstance 现在要改为单例锁定方法 requestSingleInstanceLock
搜索出错提示一一修正直到可以正常进入开发模式。

测试界面、测试功能

测试界面、测试功能
一一修正的图片、css、js资源引用问题。
一一修正其他程序错误。

尝试打包可执行程序

尝试打包可执行程序,更新可执行程序图标、任务栏托盘图标……

总结

electron工程升级工作并不难,但想要快速完成也不那么容易。
在进行的过程中会遇到各种各样的问题,需要根据错误提示进行推理,或者查阅资料,来一一解决错误。
有时候还需要一点点直觉,这种直觉是工作中积累的经验带来的。

为了以后的工作中不再重复这样的工作,我准备了一个electron脚手架工程(创建于2019年11月),里面的依赖是比较新的,目录结构也合理,可以clone后使用
http://repo.slimcloud.io:5002/boilerplate/zving-electron-vue-starter.git