阅读本文需要一点 JS 基础和阅读的耐心,我特么自己写完后发现这文章咋这么长啊。。。如果你认真看完算我输!
另我专门做了个 vue-nw-seed 项目,里面包含了我这篇文章里的所有的点和一些别的优化,方便大家快速开发。
一、最小侵入性使用 vuejs-templates
建构 NW.js
应用
在 Vue 圈里,最方便的项目建构方式应该是 vue-cli
,这整个生态里面最便捷的又应该是 webpack 这个模板。再对这个模板比较熟悉了后,就开始想能不能根据这个模板快速构建我们需要的 NW.js 项目呢?
蛤蛤,答案当然是可以的。
最开始的思路比较笨重,如果你时间多,可以去看看我以前的思路 用 vue2 和 webpack 快速建构 NW.js 项目(1) 2333。在我连续加班一个月后整出了我们 豆豆数学
第一版后,稍微有了点空闲时间,就重新翻看了一下 NW.js 的文档,有点小发现啊。
Manifest Format
清单文件中的小发现
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).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.
这个意思是说中说 main
字段可以写一个 URL
,也能写 index.html
或者 script.js
文件, node-remote
字段说允许哪些远程页面调用 Node 方法。
这组合起来就可以完全无侵入的使用 vuejs-templates
建构 NW.js
应用!
整体思路就是设置 package.json
的 main 字段为 vue 项目的起始地址,然后把 node-remote 设置为
允许全部的 JS 调用 Node 。
先上个效果
1、先安装 NW 的开发依赖
依然推荐 nwjs/npm-installer
npm install nw --save-dev
网络不要的情况下,请参考之前写的文章中关于 用 npm 安装 NW.js 部分。
2、配置 webpack
相对于第一版,这次对于模板标配的建构配置改动相当小。
把 build/webpack.base.conf.js 中新加个 target
字段就搞定。大概就是这样
module.exports = {
entry: { ... },
output: { ... },
target: 'node-webkit',
...
}
简单吧。
3、修改 package.json
添加或者修改 main
字段为你的 vue 项目启动地址,再添加 node-remote
为
在配置下 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": ""
}
4、修改 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.js 应用
推荐使用官方的包 nw-builder ,虽然好久都没咋更新过了。。。
整体思路 :先打包 vue 项目,再用 Node.js 整理形成一个 package.json
文件到 dist 目录中去。再用 nw-builder 打包出 NW 应用。
先看效果,增加信心。
1、安装 nw-builder
npm install nw-builder --save-dev
这个过程仅仅是安装了打包 NW 的包装器,其要用到的 runtime 要在使用的时候才下载。
如果网络不好。。。可以自己先想个办法直接复制一份 runtime 到 cacheDir 目录中。
2、增加 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: {
//...
}
}
3、增加 ./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!')
})
})
4、在 ./build/build.js
中增加打包入口
增加下面这一行代码在 webpack 打包完成的回调中
// start build nw.js app
require('./build-nw.js')
简单 4 部就完成了打包,是不是异常清晰和简单。蛤
三、打包 windows 下的 setup.exe 文件
这个部分,我之前也写了一篇文章 打包NW.js应用和制作windows安装文件 里面有比较详细的打包介绍。
但,在我们借助了 nw-builder 做了 NW 的打包后,仅仅打安装包就比较简单了,所以今天我就简写,节约大家的时间和生命。
主要思路:用 Node.js 操作 iss 文件,再借助官方推荐的 innosetup 进行打包。
继续录一个 打包 exe 文件的 demo
1、安装相关依赖
npm install iconv-lite innosetup-compiler --save-dev
2、创建 ./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 文件。
2、继续加配置
那句话咋说的来着,配置大于约定。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}' // 如果有就写上
}
}
3、新增 ./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
})
}
4、再配置这个打包的入口
在我们上文提到的打包 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 或者私信讨论
谢谢!您的支持是我继续更新下去的动力。