NW.js开发环境搭建
简介:NW.js是什么?
NW.js基于Chromium和Node.js。它使您可以直接从浏览器中调用Node.js代码和模块,也可以在应用程序中使用Web技术。此外,您可以轻松地将Web应用程序打包到本机应用程序。
1. 选择Build Flavors SDK
NW.js支持各种构建样式以减小应用程序大小。当前,NW.js支持以下构建风格:
- SDK风格(SDK):内置了对DevTools和NaCl插件的支持。SDK风味与0.13.0之前的版本具有相同的功能(适用于开发调试环境);
- 正常风格(Normal):是不带DevTools和NaCl插件支持的最低版本(适用于发布环境)。
建议您选择SDK构建风格来开发应用程序,这使您可以使用DevTools调试应用程序。
2. 下载安装NWJS
对于国内用户,如果下载连接下载速度太慢的话,可以考虑的可选项是:
或者进入官网下载根据自己需要下载对应的版(我这里下载的是mac版本的v0.44.5,osx-x64,normal
v0.44.5,osx-x64,sdk
-
下载后直接解压,将
nwjs.app
拖到应用程序中(便于从桌面点击图标启动)
-
设置别名和环境变量(以便从命令行可直接启动)
#打开bash_profile环境变量配置文件 vim ~/.bash_profile #设置环境变量-nwjs的别名 alias nw="/Applications/nwjs.app/Contents/MacOS/nwjs" #应用环境变量 source ~/.bash_profile #命令行输入nw 回车启动即可(等同于桌面点击图标启动) nw
启动后如下如:
3. 创建第一个应用
-
创建package.json文件
通过node.js初始化一个项目(首先,确保你已经安装好了
node.js
的环境),生成package.json
文件,package.json
是JSON 格式格式的配置文件.main
属性定义了应用首页, 如本例的"index.html"
.name
则定义了应用名称. 具体查看 配置文件章节.#创建项目目录 mkdir NWDemo #进入项目根目录 cd NWDemo #执行初始化生成package.json npm init
package.json
{ "name": "test1", "version": "1.0.0", "description": "", "main": "index.html", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
您可以将
"main"
属性设置如"main.js"
的js文件. 该文件在应用启动时默认不打开窗口并在后台执行。 您可以稍后进行一些初始化并手动打开窗口。 例如:// 初始化你的应用程序之后 ... nw.Window.open('index.html', {}, function(win) {});
-
创建入口页面
index.html
<!DOCTYPE html> <html> <head> <title>Hello World!</title> </head> <body> <h1>Hello World!</h1> </body> </html>
4. 运行应用
进入我们创建的项目中,执行nw .
运行当前应用(nw .
是NW.js执行文件)
- Windows系统中是
nw.exe
; - Linux系统中是
nw
; - Mac系统中是
nwjs.app/Contents/MacOS/nwjs
;
#进入项目根目录
cd NWDemo
#运行
nw .
【注意】
Windows系统中 , 可拖拽包含 package.json
的文件夹至 nw.exe
直接运行应用。
5. 打包应用
可以使用以下工具自动完成打包NW.js应用进行发布 .
- nwjs-builder-phoenix (推荐:用于为Windows,macOS和Linux构建和打包可分发的NW.js应用程序。)
- nw-builder
或者可以使用以下步骤手动构建应用 ,具体步骤参考手动构建应用
这里使用nwjs-builder-phoenix
构建方式详细使用参见github
6. APIs
详细使用参见地址
扩展
1. 配置文件(package.json)
{
"name": "nw-demo", #应用名称,应确保该字段内容全局唯一性
"main": "index.html", #应用起始页
"nodejs":true, #是否支持Node
"node-main":"xxxx", #指定Node.js脚本文件路径并且它将在加载DOM窗口之前启动Node环境时执行
"domain":"xxxx", #指定主机域名
"single-instance":false, #是否以单实例运行(false则允许应用多开,默认 true)
"bg-script":"xxxx", #应用启动时执行的后台脚本
/**
*窗体样式控制(窗口子字段默认情况被继承到使用 window.open()或 <a target="_blank">打开的子窗口
*未继承子字段将被设置为打开窗口时的默认值)
*列表如下:
* fullscreen -> false
* kiosk -> false
* position -> null
* resizable -> true
* show -> true
*/
"window":{
"id":"xxx", #内含窗口尺寸与位置的状态的窗口ID,打开同ID的窗口时会还原该状态
"title":"xxx", #NW.js创建的窗口标题 . 在应用启动时显示的标题信息
"width":200, #窗口宽高
"height":200, #窗口高
"toolbar":true, #是否显示导航栏中的工具条
"icon":"xxxx", #窗口图标路径
"position":"xxx", #窗口位置:默认 null(不固定) , center(屏幕居中) , mouse(鼠标所在位置)
"min_width":100, #窗口最小宽高
"min_height":100,
"max_width":500, #窗口最大宽高
"max_height":500,
"as_desktopLinux":false, #X11环境下,作为桌面背景显示(true显示, false不显示)
"resizable":true, #是否可调整窗口大小(在OS X上将该属性设置为 false,并将frame设置true,
#用户还是可以将窗口全屏显示。只有将全屏也设置为 false才可禁用全屏控件。
"always_on_top":false, #是否允许窗口始终置顶(在其余窗口之上,true允许, false不允许)
"visible_on_all_workspaces Mac & Linux",: #支持多工作区的系统(如Mac & Linux)中,将窗口同时显示
#在所有工作区中(true允许, false不允许)
"fullscreen":false, #是否允许窗口全屏(窗体和全屏框架(frame)应当一致,窗口设置为 false时,
#则全屏框架(frame)不应设为 true,避免窗体将阻止鼠标获取屏幕边缘)
"frame":true, #窗口是否为框架(窗体和全屏框架应当一致,窗口设置为 false时,则全屏框架
#不应设为 true,避免窗体将阻止鼠标获取屏幕边缘.)
"show_in_taskbar":true, #是否允许显示在任务栏或停靠栏中(true允许, false不允许,默认 true)
"show":true, #启动时是否显示应用(true显示, false不显示)
"kiosk":true, #是否使用 Kiosk模式(该模式即应用将全屏并阻止用户离开(关闭)应用,比如常见
#的公共触摸屏演示,银行排队取票机洁界面,直到页面返回响应)
"transparent":false, #窗口是否透明(true允许, false不允许,默认 false)
#窗口的透明度由CSS中的背景透明值控制,
#使用命令行参数 --disable-transparency 可完全禁止透明功能.
#使用命令行参数 --disable-gpu 禁用GPU后,可实现透明窗体的穿透点击
},
/**
*WebKit特性控制
*/
"webkit":{
"double_tap_to_zoom_enabled":false, #是否启用两指缩放功能(true允许, false不允许,默认 false)
"plugin":true, #是否可加载扩展插件,比如Flash插件(true允许, false不允许,默认 true),
},
"user-agent":"xxx", #重写应用请求页面中的 User-Agent信息
#以下变量内容可以动态设置 User-Agent内容:
#%name: 替换配置文件中的name字段 .
#%ver: 替换配置文件中的version字段 .
#%nwver: 替换NW.js版本 .
#%webkit_ver: 替换WebKit引擎版本 .
#%osinfo: 替换系统以及CPU信息 .
"chromium-args":"xxx", #分发应用时自定义chromium命令行参数至应用(想要禁用GPU加速视频显示,
#只需添加添加参数 "chromium-args" : "--disable-accelerated-
#video".)
"crash_report_url":"xxx", #应用崩溃时,崩溃转存报告将被发送到设定的服务器
"inject_js_start":"xxx", #CSS文件执行之后 , 其他DOM或脚本运行之前 , 执行的JavaScript代码
"inject_js_end":"xxx", #页面document对象加载之后 , 触发 onload之前 , 执行的JavaScript
#代码 . 主要作为新窗口中 Window.open()的参数执行JavaScript代码
"additional_trust_anchors", #证书作为附加可用的根证书使用 , 允许连接自签名证书或者CA签发机构颁发
#证书的服务(Array - 数多个PEM编码的证书组成的数组)
"dom_storage_quota":500, #Mb为单位的DOM存数限制数量(建议设置为期望值的两倍)
/**
*其他字段
*/
"description":"xxx" #配置的描述说明,以.结束
"version":1.0, #应用版本信息
"maintainers":[{ #维护成员
"name": "Bill Bloggs",
"email": "billblogs@bblogmedia.com",
"web": "http://www.bblogmedia.com",
}],
"contributors":[{ #贡献者,与维护成员格式一致,但首个应为作者
"name": "xxx",
"email": "xxx",
"web": "xxx",
}],
"bugs":"xxx", #提交错误的网址,如mail或http地址
"licenses":"xxx", #许可证列表
"dependencies":"xxx", #必要依赖,组的顺序非常重要,较前条目具有较高优先级.
"homepage":"xxx", #网站URL地址
}
2. 使用NW.js APIs
NW.js
中的APIs都被加载到nw
全局对象中,并能够在javascript代码中直接使用
旧版本加载
nw
方式为require('nw.gui')
,返回nw
对象。
举个🌰:通过NW.js
提供的API来创建应用此单(当用户鼠标右击打开上下文菜单显示)
<html>
<head>
<title>创建上下文菜单</title>
</head>
<body>
<P>右击显示上下文菜单</P>
<script>
//1.通过NW.js 创建一个空菜单
let menu = new nw.Menu(); //js中可直接使用 nw 对象,已经被加载到全局对象中
//2.创建三个菜单项目
let menuItem1 = new nw.MenuItem({label:'菜单1',click:()=>{alert('选择菜单1')}});
let menuItem2 = new nw.MenuItem({label:'菜单2',click:()=>{alert('选择菜单2')}});
let menuItem3 = new nw.MenuItem({label:'菜单3',click:()=>{alert('选择菜单3')}});
//3.将菜单项添加到菜单中
menu.append(menuItem1);
menu.append(menuItem2);
menu.append(menuItem3);
//4.对当前html文档body标签内的正文内容设置监听事件
document.body.addEventListener('contextmenu',(ev)=>{
//阻止默认的弹出框
ev.preventDefault();
//弹出自定义的Menu菜单
menu.popup(ev.x,ev.y);
return false;
},false);
</script>
</body>
</html>
效果如下图:
PS:
- 更多HTML DOM Event事件可参见**《HTML DOM 事件》**
3. 使用Node.js API(require
)
我们除了上述在DOM中直接使用NW.js
的APIS之外,还可以在DOM中直接调用node.js
代码及模块。这样就可以通过NW.js轻松开发PC桌面应用了。
举个🌰:利用Node.js的os
模块擦护心操作系统的信息
<html>
<head>
<title>创建上下文菜单</title>
</head>
<body>
<!--这里使用Flex弹性布局-->
<div style="display:flex;flex-direction:row;justify-content:center;align-item:center">
<div style="flex:30%">您的当前系统为:</div>
<div style="flex:70%" id='os_platform'></div>
</div>
<script>
//1.将node.js中os模块脚本赋值给 mOS对象
let mOS = require('os');
//2.通过document的getElementById方法获取对应的div对象,然后通过innerHTML修改div内容
document.getElementById('os_platform').innerHTML=mOS.platform();
</script>
</body>
</html>
PS:
- 您可以在NW.js中通过
npm
进行模块的安装然后使用。 - 详细Flex布局参见《Flex 布局语法教程》
- 【注意】当使用
npm install
构建原生模块时无法兼容 NW.js ABI. 如需使用则需要根据nw-gyp
进行NW.js的重建. 详细参考使用原生模块章节.
4. 开发工具与调试
开发工具只能在SDK构建方式中使用。
-
Open Developer Tools 开启开发工具
Windows和Linux系统中使用快捷键
F12
开启 , Mac系统中使用⌥⌘i
.此外 , windows系统中可以使用win.showDevTools()`开启开发工具进行编程
-
调试Node.js模块
NW.js默认进行独立环境模式运行,调试Node.js模块需要在引用中右键并选择
Inspect Background Page
,当运行到Node.js模块代码时,调试器会自动聚焦并暂停运行。混合模式下,Node.js模块可以在开发工具中直接进行调试。
-
远程调试
使用命令行参数
--remote-debugging-port=port
指定端口进行监听。例如,运行nw --remote-debugging-port=9222
,通过浏览器访问http://localhost:9222/进行远程调试
。 -
开发工具扩展
开发工具支持全部扩展,包括
ReactJS
等使用扩展需要在配置文件
manifest.json
添加chrome-extension://*
权限,并在nw运行时增加--load-extension=path/to/extension
命令行参数 . 扩展的文件从Chrome应用商店安装之后拷贝到本地Chrome浏览器目录下 .
5. NW.js中独立环境和混合环境模式
NW.js基于Chrome应用构建,因此NW.js在开始运行时候,自动完成后台加载。当创建一个窗口时,同时创建一个JavaScript环境。
NW.js中,默认情况下,Node.js模块加载到后台运行环境。
5.1 独立环境模式
除浏览器环境,NW.js默认在后台增加Node.js环境运行Node模块,这样NW.js同时拥有两个JavaScript环境:
浏览器环境
和Node环境
5.1.1 浏览器环境
-
加载脚本
<script>
标签- jQuery的
$.getScript()
- RequireJS 。
-
全局对象
-
创建新浏览器环境
创建新的frame或者窗口时 , 将得到一个新的浏览器环境
-
访问
Node.js
和NW.js
的API将Node环境对象拷贝到浏览器环境中 , 这样运行在浏览器环境的脚本能够访问Node.js对象
nw
– 所有NW对象 NW.js APIsglobal
– NW环境全局对象; 等同nw.global
require
– 加载Node.js模块的方法; 等同nw.require()
, 但不支持通过require('nw.gui')
加载NW.js模块.process
– Node.js模块中process模块; 等同nw.process
Buffer
– Node.js模块中Buffer类
5.1.2 Node环境
-
加载脚本
- 通过Node.js的API中的
require()
加载脚本; - 通过配置文件中
node-main
加载脚本;
- 通过Node.js的API中的
-
全局对象
- js内建对象
- Node.js全局对象(如
__dirname
,process
,Buffer
等)
【注意】Node环境不能使用Web APIs,下面会介绍访问浏览器环境和NW.js的API
-
创建新的Node环境
- 通过
Window.open()
创建新窗口,并且参数new_instance
设置为true时; - 命令行启动NW.js时,参数增加
--mixed-context
进入混合环境模式;
- 通过
-
访问浏览器环境和NW.js的API
Node环境中,没有浏览器或NW.js APIS,如
alert()
、document.*
、new.Clipboard
等,想访问浏览器APIs**必须传递相应的对象
**,如window
举个🌰
Node脚本(
test.js
)//node环境中访问浏览器的API需要浏览器环境传入对应的el对象 exports.setText = (el) => { el.innerHTML = 'hello'; };
浏览器(
index.html
)<html> <head> ... </head> <body> <div id="el"></div> <script> //加载 test.js Node模块 var myscript = require('./test.js'); //调用Node模块setText方法将`el`元素传入Node函数 myscript.setText(document.getElementbyId('el')); // "hello"将显示在`el`标签中 </script> </body> </html>
5.2 混合环境模式
当使用--mixed-context
命令行参数运行NW.js时 , 当浏览器环境被创建的同时创建了Node环境 .
-
混合模式中加载脚本
NW.js可以通过两种方式使用混合环境模式 , 一是使用
--mixed-context
命令行参数 , 二是在配置文件中添加chromium-args
属性.package.json
{ "name": "test-context", "main": "index.html", "chromium-args": "--mixed-context" //chromium-args属性指定使用混合模式 }
页面或Node.js中使用
require()
方式加载脚本运行在同样的环境中 . -
全局对象
混合环境模式 , 您可以在Node模块中使用所有浏览器和NW.js API,反之亦然
node.js文件(test.js)
//导出showAlert函数 exports.showAlert = function() { //因为开启了混合环境摸模式,可以在Node中直接使用浏览器的alert API alert("我正在Node模块中运行!"); };
Index.html
<html> ... <body> <script> //加载 test node模块 var test = require('./test'); //调用模块的showAlert方法 myscript.showAlert(); // 我正在Node模块中运行! </script> </body> </html>
5.3 混合环境模式和独立环境模式对比
独立环境模式的优势是不会出现类型检查问题 .
混合环境模式的缺点是不能轻易的分享变量 . 环境间分享变量 , 需要将变量放入其他环境能够访问的通用环境中 . 或者可以使用window.postMessage()
API在环境之间发送和接收信息 .
6. JavaScript源码保护
-
源码保护目的
应用中的javascript源代码能够编译为本地二进制代码进行保护,NW.js能够加载编译之后的代码,应用作为产品发布时可以将代码进行编译!
-
如何编译和加载
-
编译
JS源码编译为本地二进制代码需要使用
nwjc
工具,同时需要提供SDK构建方式的NW.nwjc source.js binary.bin
*.bin
文件需要发布到应用中,可以任意命名bin文件。 -
加载
NW.js 加载已编译的JS文件
nw.Window.get().evalNWBin(frame,'binary.bin');
Win.evalNWBin()方法中的参数与
Window.eal()
方法相同,第一个参数为目标frame(null为主frame),第二个参数为已编译的bin文件 -
加载远程已编译的JS文件
NW.js可以从远程(例如AJAX)获取已编译的JavaScropt,并且即时执行。
//创建请求实例HMLHttpRequest var xhr = new HMLHttpRequest(); //设置响应类型-ArrayBuffer对象二进制数据(告诉服务器期望的响应格式) xhr.responseType = 'arraybuffer'; //设置请求方式和地址 xhr.open('GET',url,true); //发起请求 xhr.send(); //接收响应 xhr.onload=()=>{ //接收到响应使用NW加载已编译的js文件 nw.Window.get().evalNWBin(null,xhr.response); }
**PS:**已编译代码在浏览器环境中执行 . 可以像其他运行在浏览器环境中的其他脚本 , 就像您使用任何Web API(如DOM)以及NW.js API和Node API
-
-
不足
已编译代码不支持跨平台也不兼容不同版本的NW.js的版本,因此在打包应用时需要在各自系统平台中运行
nwjc
.
7. 应用签名
应用签名可阻止正式应用中加载未签名的文档。
注意:应用签名并不能阻止别有用心的人黑进你的应用,或者使用其他NW代码加载你的应用,可考虑使用C++编写,并使用Node.js模块和NaCl加载,或者结合使用nwjc
对源码进行编译为本地二进制文件后使用!
-
签名文件
NW.js的应用签名文件为
verified_contents.json
,它提供了密钥对,该文件由sign.py
工具与私钥文件private_key.pem
所创建,公钥已经内置于NW.js应用中。 -
如何签名
要运行签名的应用程序,在应用程序目录中调用
nw --verify-content=enfore_strict
命令执行严格验证模式,之后显示签名页面,完成签名动作。在此之后对
index.html
进行任意修改,NW将报告文件已损坏并立即退出。 -
签名步骤
使用密钥对签名应用,步骤如下:
- 切换到应用目录中
- 确认
verified_contents.json
或computed_hashes.json
文件不在当前目录(如果存在删除) - 运行
payload
生成sign.py
所需的配置文件payload.json
文件 - 运行
python sign.py > /tmp/verifid_content.json
,需要注意 tmp目录不能是应用所在目录 - 将生成的
verified_contents.json
文件拷贝到应用目录,完成签名动作。
-
使用自己密钥对重新构建应用
要使用您自己的密钥对,您需要重建NW,并命令行参数
--verify-content=
默认设置为enfoce_strict
openssl genrsa -out private_key.pem 2048
生成密钥对,输出公私钥文件- 运行
python convertkey.py
,它会将公钥转换为C源代码 - 将生成的代码复制到NW安装包的
content/nw/src/nw_content_verifier_delegate.cc
,替换原文件默认的key值 - 更改文件中第73行为
Mode experiment_value = ContentVerifierDelegate::ENFORCE_STRICT;
- 重新构建NW
7. 自动更新
详细使用参见此链接
8. Node.js 自定义模块
Node应用由模块组成,每个文件就是一个模块,有自己的作用域
。在一个文件里面定义的变量、函数、类,都是私有的
,对其他文件不可见。每个模块内部,module
变量代表当前模块。这个变量是一个对象
它的exports属性(即module.exports
)是对外的接口。加载某个模块
,其实是加载该模块的module.exports属性。
-
module.export(
导出模块
)module.exports
是对外的接口。加载某个模块
,其实是加载该模块的module.exports属性。举个🌰
/** *创建一个名为test.js的 Node.js文件 */ var x = 5; var addX = (value)=>{ return value + x; } module.export.x = x; module.export.addX = addX;
上面代码通过
module.export
导出变量x和函数addX -
require(
加载模块
)/** *创建名为 index.js 的 javascript文件 */ //通过require加载模块 var example = require('./test.js'); //调用模块中的变量和函数 console.log(example.x); // 5 console.log(example.addX(1)); // 6
-
exports 与 module.exports区别
为了方便我们可以直接在exports
对象
上添加方法,表示对外输出的接口,如同在module.exports
上添加的一样。注意:不能直接将exports变量指向一个值,因为这样等于切段了exports与module.exports的联系错误写法
export='将exports变量指向该值'
正确写法(car.js)
-
导出变量
//导出color变量 export.carColor='red'
-
导出函数
//导出setCarColor函数 export.setCarColor=(color)=>{ console.log('color:' + color); }
-
导出对象
/** *导出Car对象包含如下属性和方法 *@attribute:[brand,color,price] *@method:[setCarColor,] */ export.Car={ brand:'', //品牌 color:'', //颜色 price:20000, //价格 //设置品牌 setBrand:(brand)=>{ this.brand = brand; }, //设置颜色 setCarColor:(color)=>{ this.color = color; }, //设置价格 setPrice:(price)=>{ this.price=price; }, //获取汽车信息 getCarInfo:()=>{ return '这是一辆'+this.price+this.color+this.brand+'汽车'; }, }
在JS(index.js)中使用Node.js
//加载car.js 模块 let carNodeJs = require('./car'); _printLog=()=>{ custom.Car.setBrand('兰博基尼'); custom.Car.setColor('红色'); custom.Car.setPrice(2000000); alert(custom.Car.getCarInfo()); }
-
9. ES6与NodeJS模块导入导出区别
NodeJs
-
在Node模块中,采用的是commonjs规范,也就是使用**
require
方式引入模块,而使用module.exports
**导出接口 -
如何检测Node.js对ES6的支持
- 命令行全局安装
es-checker
- 执行 es-checker
- 红色表示暂不支持的
- 命令行全局安装
ES6
- Es6模块中并没有采用node中require导入模块的方式,导入(import),导出(export)