用 vue2 和 webpack 快速建構 NW.js 項目(3)

閱讀本文須要一點 JS 基礎和閱讀的耐心,我特麼本身寫完後發現這文章咋這麼長啊。。。若是你認真看完算我輸! javascript

另我專門作了個 vue-nw-seed 項目,裏面包含了我這篇文章裏的全部的點和一些別的優化,方便你們快速開發。html

1、最小侵入性使用 vuejs-templates 建構 NW.js 應用

在 Vue 圈裏,最方便的項目建構方式應該是 vue-cli ,這整個生態裏面最便捷的又應該是 webpack 這個模板。再對這個模板比較熟悉了後,就開始想能不能根據這個模板快速構建咱們須要的 NW.js 項目呢?vue

蛤蛤,答案固然是能夠的。java

最開始的思路比較笨重,若是你時間多,能夠去看看我之前的思路 用 vue2 和 webpack 快速建構 NW.js 項目(1) 2333。在我連續加班一個月後整出了咱們 豆豆數學 初版後,稍微有了點空閒時間,就從新翻看了一下 NW.js 的文檔,有點小發現啊。node

Manifest Format 清單文件中的小發現webpack

main
{String} which HTML page should be opened or which JavaScript file should be executed when NW.js starts.
You can specify a URL here. You can also specify just a filename (such as index.html or script.js) or a path (relative to the directory where your package.json resides).ios

node-remote
{Array} or {String} Enable calling Node in remote pages. The value controls for which sites this feature should be turned on. Each item in the array follows the match patterns used in Chrome extension.git

這個意思是說中說 main 字段能夠寫一個 URL ,也能寫 index.html 或者 script.js 文件, node-remote 字段說容許哪些遠程頁面調用 Node 方法。github

這組合起來就能夠徹底無侵入的使用 vuejs-templates 建構 NW.js 應用!web

總體思路就是設置 package.json 的 main 字段爲 vue 項目的起始地址,而後把 node-remote 設置爲 <all_urls> 容許所有的 JS 調用 Node 。

先上個效果
seed-npm-run-dev

一、先安裝 NW 的開發依賴

依然推薦 nwjs/npm-installer

npm install nw --save-dev

網絡不要的狀況下,請參考以前寫的文章中關於 用 npm 安裝 NW.js 部分。

二、配置 webpack

相對於初版,此次對於模板標配的建構配置改動至關小。

把 build/webpack.base.conf.js 中新加個 target 字段就搞定。大概就是這樣

module.exports = {
  entry: { ... },
  output: { ... },
  target: 'node-webkit',
  ...
}

簡單吧。

三、修改 package.json

添加或者修改 main 字段爲你的 vue 項目啓動地址,再添加 node-remote<all_urls> 在配置下 NW.js 的 window 或者其餘配置就行,大概就是這樣:

{
  "name": "vue-nw-seed",
  "version": "0.1.0",
  // ...
  "main": "http://localhost:8080",
  "window": {
    "title": "vue-nw-seed",
    "toolbar": true,
    "width": 800,
    "height": 500,
    "min_width": 800,
    "min_height": 500,
    "resizable": true,
    "frame": true,
    "kiosk": false,
    "icon": "/static/logo.png",
    "show_in_taskbar": true
  },
  "nodejs": true,
  "js-flags": "--harmony",
  "node-remote": "<all_urls>"
}

四、修改 npm run dev 打開瀏覽器爲打開 NW.js

這一部應該是最複雜的一步,但實際上,至關簡單。
增長 build/dev-nw.js

var exec = require('child_process').exec
var path = require('path')
var fs = require('fs')
var nwPath = require('nw').findpath()
var rootPath = path.resolve(__dirname, '../')
var packageJsonPath = path.resolve(rootPath, './package.json')

module.exports = runNwDev

function runNwDev(uri = '') {
  if (uri && (uri + '').trim()) {
    tmpJson = require(packageJsonPath)
    tmpJson.main = uri
    fs.writeFileSync(packageJsonPath, JSON.stringify(tmpJson, null, '  '), 'utf-8')
  }

  var closed
  var nwDev = exec(nwPath + ' ' + rootPath, { cwd: rootPath }, function(err, stdout, stderr) {
    process.exit(0)
    closed = true
  })

  nwDev.stdout.on('data', console.log)
  nwDev.stdout.on('error', console.error)

  // 退出時也關閉 NW 進程
  process.on('exit', exitHandle)
  process.on('uncaughtException', exitHandle)

  function exitHandle(e) {
    if (!closed) nwDev.kill()
    console.log(e || '233333, bye~~~')
  }
}

並修改 build/dev-server.js 文件中打開瀏覽器的那部分代碼。

// when env is testing, don't need open it
  if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
    require('./dev-nw')(uri)
  }

至此,整個開發建構就完成了,是否是幾乎無侵入性。

2、打包 NW.js 應用

推薦使用官方的包 nw-builder ,雖然很久都沒咋更新過了。。。

總體思路 :先打包 vue 項目,再用 Node.js 整理造成一個 package.json 文件到 dist 目錄中去。再用 nw-builder 打包出 NW 應用。

先看效果,增長信心。
seed-npm-run-build

一、安裝 nw-builder

npm install nw-builder --save-dev

這個過程僅僅是安裝了打包 NW 的包裝器,其要用到的 runtime 要在使用的時候才下載。

若是網絡很差。。。能夠本身先想個辦法直接複製一份 runtime 到 cacheDir 目錄中。

二、增長 config

配置大於約定,2333。
增長 manifest 要被整理的字段,最終從 ./package.json 整理到 ./dist/package.json 中。
增長 builder 字段,能夠參照 nw-builder 文檔來配置。

// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')

function resolve(dir) {
  return path.join(__dirname, '..', dir)
}

module.exports = {
  build: {
    // ...
    nw: {
      // manifest for nw
      // the fileds will merge with `./package.json` and build to `./dist/package.json` for NW.js
      // Manifest Format: http://docs.nwjs.io/en/latest/References/Manifest%20Format/
      manifest: ['name', 'appName', 'version', 'description', 'author', { main: './index.html' }, 'window', 'nodejs', 'js-flags', 'node-remote'],
      // see document: https://github.com/nwjs/nw-builder
      builder: {
        files: [resolve('./dist/**')],
        platforms: ['win32'],
        version: '0.14.7',
        flavor: 'normal',
        cacheDir: resolve('./node_modules/_nw-builder-cache/'),
        buildDir: resolve('./output'),
        zip: true,
        winIco: resolve('./static/favicon.ico'),
        buildType: 'versioned'
      }
    }
  },
  dev: {
    //...
  }
}

三、增長 ./build/build-nw.js 文件

這個文件主要作的事情就是整理出 NW.js 用的 package.json,而後再調用 nw-builder 進行打包

var exec = require('child_process').exec
var path = require('path')
var fs = require('fs')
var util = require('util')

var rootPath = path.resolve(__dirname, '../')

// get config
var config = require(path.resolve(rootPath, 'config'))

// `./package.json`
var tmpJson = require(path.resolve(rootPath, './package.json'))
var manifestPath = path.resolve(config.build.assetsRoot, './package.json')

// manifest for `./dist/package.json`
var manifest = {}
config.build.nw.manifest.forEach(function(v, i) {
  if (util.isString(v)) manifest[v] = tmpJson[v]
  else if (util.isObject(v)) manifest = util._extend(manifest, v)
})

fs.writeFile(manifestPath, JSON.stringify(manifest, null, '  '), 'utf-8', function(err, data) {
  if (err) throw err

  // start build app
  if (!config.build.nw.builder) return
  var NwBuilder = require('nw-builder')
  var nw = new NwBuilder(config.build.nw.builder)
  nw.build(function(err, data) {
    if (err) console.log(err)
    console.log('build nw done!')
  })
})

四、在 ./build/build.js 中增長打包入口

增長下面這一行代碼在 webpack 打包完成的回調中

// start build nw.js app
    require('./build-nw.js')

簡單 4 部就完成了打包,是否是異常清晰和簡單。蛤

3、打包 windows 下的 setup.exe 文件

這個部分,我以前也寫了一篇文章 打包NW.js應用和製做windows安裝文件 裏面有比較詳細的打包介紹。

但,在咱們藉助了 nw-builder 作了 NW 的打包後,僅僅打安裝包就比較簡單了,因此今天我就簡寫,節約你們的時間和生命。

主要思路:用 Node.js 操做 iss 文件,再借助官方推薦的 innosetup 進行打包。

繼續錄一個 打包 exe 文件的 demo
seed-npm-run-build

一、安裝相關依賴

npm install iconv-lite innosetup-compiler --save-dev

二、建立 ./config/setup.iss 打包配置文件

踩坑注意,不要用 utf8 存這個文件,用 ansi 格式存這個配置文件, 否則打出來的安裝包是亂碼。

; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
; This CWD is the directory where the `setup.iss`, pay attention to join the relative directory!
; 該執行目錄爲 `setup.iss` 所在的目錄,請注意拼接相對目錄

#define MyAppName "_name_"
#define MyAppAliasName "_appName_"
#define MyAppVersion "_version_"
#define MyAppPublisher "_appPublisher_"
#define MyAppURL "_appURL_"
#define MyAppExeName "_name_.exe"
#define OutputPath "_outputPath_"
#define OutputFileName "_outputFileName_"
#define SourceMain "_filesPath_\_name_.exe"
#define SourceFolder "_filesPath_\*"
#define LicenseFilePath "_resourcesPath_\license.txt"
#define SetupIconFilePath "_resourcesPath_\logo.ico"
#define MyAppId "_appId_"

[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={#MyAppId}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppAliasName}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\{#MyAppName}
LicenseFile={#LicenseFilePath}
OutputDir={#OutputPath}
OutputBaseFilename={#OutputFileName}
SetupIconFile={#SetupIconFilePath}
Compression=lzma
SolidCompression=yes
PrivilegesRequired=admin
Uninstallable=yes
UninstallDisplayName={#MyAppAliasName}
DefaultGroupName={#MyAppAliasName}

[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce

[Files]
Source: {#SourceMain}; DestDir: "{app}"; Flags: ignoreversion
Source: {#SourceFolder}; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs

[Messages]
SetupAppTitle={#MyAppAliasName} setup wizard
SetupWindowTitle={#MyAppAliasName} setup wizard

[Icons]
Name: "{commondesktop}\{#MyAppAliasName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
Name: "{group}\{#MyAppAliasName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{group}\uninstall {#MyAppAliasName}"; Filename: "{uninstallexe}"

[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent

細心的你可能已經發現了這裏面好多 _name_ 之類的東西,這玩意將要被 Node.js 替換成項目配置的信息,不須要每次手動改寫這個複雜的 iss 文件。

二、繼續加配置

那句話咋說的來着,配置大於約定。23333333
./config/index.js 文件中加上 build.nw.setup 字段,來配置要打包出來的應用的信息。

nw: {
      // ...
      setup: {
        issPath: resolve('./config/setup.iss'),  // 就是上面那個 iss
        files: path.resolve('./output', tmpJson.name + ' - v' + tmpJson.version),  // 要打包的文件目錄
        outputPath: resolve('./output/setup/'),
        outputFileName: '${name}-${version}-${platform}-setup',  // 提供 name、version、platform 三個字段進行自定義輸出文件名配置
        resourcesPath: resolve('./build/setup_resources'),  // 上面沒說的打包用的 license 和 logo。參見 https://github.com/anchengjian/vue-nw-seed/tree/master/build/setup_resources
        appPublisher: 'vue-nw-seed, Inc.',
        appURL: 'https://github.com/anchengjian/vue-nw-seed',
        appId: '{{A448363D-3A2F-4800-B62D-8A1C4D8F1115}'  // 若是有就寫上
      }
    }

三、新增 ./build/build-win-setup.js

這個文件就是用來打包 windows 下安裝包的。。。

var innosetupCompiler = require('innosetup-compiler')
var path = require('path')
var fs = require('fs')
var iconv = require('iconv-lite')

var rootPath = path.resolve(__dirname, '../')

// `./package.json`
var tmpJson = require(path.resolve(rootPath, './package.json'))

// get config
var config = require(path.resolve(rootPath, 'config'))
var setupOptions = config.build.nw.setup

fs.readdir(setupOptions.files, function(err, files) {
  if (err) throw err

  files.forEach(function(fileName) {
    if (!~fileName.indexOf('win')) return

    const curPath = path.resolve(setupOptions.files, fileName)
    fs.stat(curPath, function(err, stats) {
      if (err || stats.isFile()) return
      if (stats.isDirectory()) {
        makeExeSetup(Object.assign({}, setupOptions, { files: curPath, platform: fileName }))
      }
    })
  })
})

function makeExeSetup(opt) {
  const { issPath, files, outputPath, outputFileName, resourcesPath, appPublisher, appURL, appId, platform } = opt
  const { name, appName, version } = tmpJson
  const tmpIssPath = path.resolve(path.parse(issPath).dir, '_tmp.iss')

  return new Promise(function(resolve, reject) {
    // rewrite name, version to iss
    fs.readFile(issPath, null, function(err, text) {
      if (err) return reject(err)

      let str = iconv.decode(text, 'gbk')
        .replace(/_name_/g, name)
        .replace(/_appName_/g, appName)
        .replace(/_version_/g, version)
        .replace(/_outputPath_/g, outputPath)
        .replace(/_outputFileName_/g, getOutputName(outputFileName, { name, version, platform }))
        .replace(/_filesPath_/g, files)
        .replace(/_resourcesPath_/g, resourcesPath)
        .replace(/_appPublisher_/g, appPublisher)
        .replace(/_appURL_/g, appURL)
        .replace(/_appId_/g, appId)


      fs.writeFile(tmpIssPath, iconv.encode(str, 'gbk'), null, function(err) {
        if (err) return reject(err)

        // inno setup start
        innosetupCompiler(tmpIssPath, { gui: false, verbose: true }, function(err) {
          fs.unlinkSync(tmpIssPath)
          if (err) return reject(err)
          resolve(opt)
        })
      })
    })
  })
}

function getOutputName(str, data) {
  return str.replace(/\$\{(.*?)\}/g, function(a, b) {
    return data[b] || b
  })
}

四、再配置這個打包的入口

在咱們上文提到的打包 NW 應用的那個文件中 ./build/build-nw.js 中的最後打包完成的回調里加個調用入口

// build windows setup
    if (config.build.noSetup) return
    if (~config.build.nw.builder.platforms.toString().indexOf('win')) require('./build-win-setup.js')

此次簡潔吧,4 部就完成了打包。
來看效果。

原文持續更新: https://github.com/anchengjian/anchengjian.github.io/blob/master/posts/2017/vuejs-webpack-nwjs-2.md,同時,若是對您有用,幫我點個 star 吧,寫這玩意不容易啊。

若是你真的看到這兒了,我也就輸了。。。
那就順便看看 vue-nw-seed 這個項目吧,裏面包含了我這篇文章裏的全部的點和一些別的優化。
但願還有其餘需求的朋友能夠提 issue 或者私信討論

謝謝!您的支持是我繼續更新下去的動力。

相關文章
相關標籤/搜索