閱讀本文須要一點 JS 基礎和閱讀的耐心,我特麼本身寫完後發現這文章咋這麼長啊。。。若是你認真看完算我輸! javascript
另我專門作了個 vue-nw-seed 項目,裏面包含了我這篇文章裏的全部的點和一些別的優化,方便你們快速開發。html
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).iosnode-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 。
先上個效果
依然推薦 nwjs/npm-installer
npm install nw --save-dev
網絡不要的狀況下,請參考以前寫的文章中關於 用 npm 安裝 NW.js 部分。
相對於初版,此次對於模板標配的建構配置改動至關小。
把 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) }
至此,整個開發建構就完成了,是否是幾乎無侵入性。
推薦使用官方的包 nw-builder ,雖然很久都沒咋更新過了。。。
總體思路 :先打包 vue 項目,再用 Node.js 整理造成一個 package.json
文件到 dist 目錄中去。再用 nw-builder 打包出 NW 應用。
先看效果,增長信心。
npm install nw-builder --save-dev
這個過程僅僅是安裝了打包 NW 的包裝器,其要用到的 runtime 要在使用的時候才下載。
若是網絡很差。。。能夠本身先想個辦法直接複製一份 runtime 到 cacheDir 目錄中。
配置大於約定,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 部就完成了打包,是否是異常清晰和簡單。蛤
這個部分,我以前也寫了一篇文章 打包NW.js應用和製做windows安裝文件 裏面有比較詳細的打包介紹。
但,在咱們藉助了 nw-builder 作了 NW 的打包後,僅僅打安裝包就比較簡單了,因此今天我就簡寫,節約你們的時間和生命。
主要思路:用 Node.js 操做 iss 文件,再借助官方推薦的 innosetup 進行打包。
繼續錄一個 打包 exe 文件的 demo
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 或者私信討論
謝謝!您的支持是我繼續更新下去的動力。