您现在的位置是:网站首页> JS服务端

保护 Node.js 项目的源代码

摘要

保护 Node.js 项目的源代码


1.jpg



AI辅助

pkg打包问题

保护nodejs源码

NodeJs 源码保护

JS生成字节码生成技术,用字节码保护商业NodeJS源码!

nodejs代码加密



AI辅助

保护代码


####【豆包】

Q:有没有好的办法分发部署给第三方时保护nodejs的源码

A:..

Q:他们可以将页面和资源图片css之类全打包到一个文件吗

A:..

Q:pkg打包工具需要独立安装吗

A:..

Q:pkg会自动打包主文件所有文件和目录,该市分析程序需要哪些文件而打包

A:..

Q:缺文件了如何处理

A:...

Q:给下pkg打包详细说明和完整实例

A:...

pkg 打包 Node.js 项目 完整详细教程(含实战示例)

pkg 是目前 Node.js 打包成独立可执行文件(exe / 二进制) 最成熟、最简单的工具,打包后无需安装 Node.js/npm,直接双击运行,源码完全隐藏,完美满足你给第三方分发部署的需求!

我给你整理了 从零到打包成功的完整步骤 + 可直接复制的实战项目,跟着做就能成功。

一、pkg 核心优势(为什么用它)

✅ 完全隐藏源码 → 生成二进制文件,看不到 JS 代码

✅ 无需运行环境 → 第三方电脑不用装 Node.js、npm

✅ 跨平台输出 → 一次打包,生成 Windows、Linux、macOS

✅ 支持所有 Node.js 项目 → Express、Koa、Nest、普通脚本、甚至 Next 服务端

✅ 简单零配置 → 1 行命令完成打包

二、环境准备

1.安装 Node.js(16/18/20 都行,推荐 18)

2.全局安装 pkg

bash

运行

npm install -g pkg

三、完整实战示例(Express 接口项目)

我们做一个可直接交付第三方的 Express 服务项目,最终打包成 exe / 可执行文件。

1. 创建项目结构

plaintext

my-pkg-app/

├── index.js       # 入口文件(核心)

├── package.json   # 依赖配置

└── config.json    # 外置配置文件(可选)

2. 编写代码

① package.json

json

{

  "name": "my-pkg-app",

  "version": "1.0.0",

  "main": "index.js",

  "dependencies": {

    "express": "^4.18.2"

  },

  "pkg": {

    "assets": ["config.json"],  // 打包静态资源

    "targets": [

      "node18-win-x64",   // Windows 64位

      "node18-linux-x64", // Linux 64位

      "node18-macos-x64"  // macOS

    ],

    "outputPath": "dist" // 输出到 dist 文件夹

  }

}

② index.js(入口)

javascript

运行

const express = require('express');

const fs = require('fs');

const path = require('path');


const app = express();

const PORT = 3000;


// 读取外置配置(不会被打包,可让第三方修改)

const configPath = path.join(__dirname, 'config.json');

const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));


// 测试接口

app.get('/', (req, res) => {

  res.send(`

    <h1>pkg 打包成功!</h1>

    <p>项目名称:${config.appName}</p>

    <p>版本:${config.version}</p>

  `);

});


app.listen(PORT, () => {

  console.log(`服务已启动:http://localhost:${PORT}`);

  console.log(`外置配置读取成功:`, config);

});

③ config.json(外置配置,第三方可修改)

json

{

  "appName": "我的分发服务",

  "version": "1.0.0",

  "author": "内部使用"

}

3. 安装依赖

bash

运行

npm install

四、关键:pkg 打包命令

方式 1:最简打包(自动读取 package.json 配置

bash

运行

pkg .

方式 2:手动指定打包(推荐)

bash

运行

pkg index.js -c package.json

方式 3:只打 Windows 包(测试最快)

bash

运行

pkg index.js -t node18-win-x64 -o dist/my-app.exe

五、打包成功后输出文件

plaintext

dist/

├── my-app.exe        # Windows 64位(双击直接运行)

├── my-app-linux      # Linux 64位

└── my-app-macos      # macOS

六、给第三方交付方式(最安全)

你只需要把这些发给对方:

my-app.exe(服务主程序,无源码)

config.json(外置配置,可修改端口、数据库等)

对方使用方法:

双击 my-app.exe

浏览器打开 http://localhost:3000

全程不需要安装任何环境!

七、pkg 高级使用技巧(必看)

1. 打包静态资源(html、图片、配置)

在 package.json 里配置:

json

"pkg": {

  "assets": ["public/**/*", "config.json", "views/**/*"]

}

2. 支持原生模块(.node 文件,如 SQLite、串口)

bash

运行

pkg index.js -t node18-win-x64 --native

3. 压缩体积(从 100MB → 30MB)

使用 UPX 压缩(必须安装 UPX)

bash

运行

pkg index.js -t node18-win-x64 --compress

4. 不打包 Node.js 运行环境(极小体积)

适合对方电脑已装 Node

bash

运行

pkg index.js -t node18 --no-native

八、常见问题(99% 的人都会遇到)

1. 打包后找不到文件 / 路径错误

✅ 解决方案:必须用 path.join(__dirname, '文件')

js

// 错误

fs.readFileSync('./config.json')


// 正确

fs.readFileSync(path.join(__dirname, 'config.json'))

2. 打包后运行闪退

✅ 解决方案:CMD 运行查看错误

bash

运行

my-app.exe

3. 支持的 Node 版本

node18(最稳定)

node20

node16

4. 支持的平台

win-x64 Windows

linux-x64 Linux

macos-x64 macOS

九、最适合你的企业级方案(直接用)

你给第三方分发的最终流程

开发完成 Node.js 项目

使用 pkg 打包成 exe / 二进制

只交付:可执行文件 + 外置配置文件

源码 100% 保护,无法破解

总结

pkg = Node.js 代码 → 独立可执行文件

无需 Node 环境,双击运行,源码完全隐藏

支持 Express、Koa、API 服务、工具脚本

跨平台:Windows / Linux /macOS

配置简单,1 行命令打包


Q:给一个next.js开发pkg打包的完整例子

A:...



pkg打包问题

下载node出问题,手动下载

pkg 打包nodejs程序是否可以指定打包后的exe文件图标



下载node出问题,手动下载

第一步:找到 pkg 的缓存目录

根据你使用的操作系统,找到缓存目录:

Windows: C:\Users\<你的用户名>\.pkg-cache

macOS / Linux: ~/.pkg-cache

在你的报错信息中,有这么一行:

{"tag":"v3.4","name":"node-v16.16.0-win-x64"}


这说明你需要把文件放在 v3.4 这个子目录里。因此,完整的目录路径是:

C:\Users\<你的用户名>\.pkg-cache\v3.4\

特别提醒:如果你设置了 PKG_CACHE_PATH 环境变量来指定缓存目录,那么你需要去该环境变量指向的目录下寻找 v3.4 文件夹。


第二步:下载正确的文件

1.访问 pkg 官方维护的二进制文件仓库:https://github.com/vercel/pkg-fetch/releases。

2.在页面中找到并下载与你报错信息完全匹配的文件。根据你的报错,你需要下载:

node-v16.16.0-win-x64


第三步:重命名并放入缓存目录

1.将下载好的 node-v16.16.0-win-x64 文件重命名为 fetched-v16.16.0-win-x64。

2.把这个重命名后的文件,移动或复制到第一步找到的 v3.4 文件夹内

完整的例子代码

NodeJS.rar

nodejs打包例子.rar



pkg 打包nodejs程序是否可以指定打包后的exe文件图标

可以,pkg 支持在打包时为 Windows 平台的 .exe 文件指定自定义图标。

一、核心前提

仅支持 Windows 平台(.exe)。

图标文件必须是 .ico 格式(推荐包含多尺寸:16×16、32×32、48×48、256×256)。

确保已全局安装 pkg:npm install -g pkg。

二、两种指定方式

1. 命令行参数(--icon)

打包时直接在命令后添加 --icon 并指定 .ico 路径:

bash

运行

# 基础用法

pkg index.js --icon ./app.ico --targets node18-win-x64 --output my-app.exe


# 完整示例(指定入口、图标、目标平台、输出名)

pkg . --icon ./assets/icon.ico -t node18-win-x64 -o dist/my-app.exe

2. package.json 配置(推荐)

在 package.json 中添加 pkg 字段,指定 icon 路径:

json

{

  "name": "my-node-app",

  "version": "1.0.0",

  "main": "index.js",

  "pkg": {

    "icon": "./assets/app.ico", // 图标路径(相对项目根目录)

    "assets": ["assets/**/*"],  // 可选:打包其他资源

    "targets": ["node18-win-x64"]

  }

}

配置后直接运行 pkg . 即可自动应用图标。

三、常见问题与解决

图标不显示

刷新 Windows 图标缓存:重启资源管理器(taskkill /f /im explorer.exe && explorer.exe)。

确保 .ico 文件路径正确、格式合规。

打包后 exe 损坏 / 闪退

避免用 Resource Hacker 等工具事后修改已打包的 exe,优先用 pkg 原生 --icon 方式。

若需批量 / 自动化修改,可使用 resedit 等库在打包后安全替换图标。

跨平台注意

macOS 打包为 .app 时,pkg 不支持原生指定 .icns 图标,需手动替换或用 electron-builder 等工具。




保护nodejs源码

SaaS(Software as a Service,软件即服务),是一种通过互联网提供软件服务的模式。服务提供商会全权负责软件服务的搭建、维护和管理,使得他们的客户从这些繁琐的工作中解放出来。对于许多中小型企业而言,SaaS 是采用先进技术的最好途径。


然而,对于大型企业而言,情况有所不同。出于产品定制、功能稳定以及掌握自身数据资产等方面的考虑,即使成本增加,他们也更乐意把相关服务部署在企业自己的硬件设备上,也就是常说的私有化部署。


在私有化部署的过程中,服务提供商首先要确保自己的源代码不被泄露,否则产品就可以随意复制和更改,得不偿失。传统的后端运行环境,如 Java、.NET,其源代码是经过编译才部署到服务器上运行的,不存在泄露的风险。而对于应用越来越广泛的 Node.js 而言,运行的则是源代码。即使经过压缩混淆,也可以很大程度地还原。


本文介绍一种可用于 Node.js 端的代码保护方案,使得 Node.js 项目也可以放心地进行私有化部署。


原理

当 V8 编译 JavaScript 代码时,解析器将生成一个抽象语法树,进一步生成字节码。Node.js 有一个叫做 vm 的内置模块,创建 vm.Script 的实例时,只要在构造函数中传入 produceCachedData 属性,并设为 true,就可以获取对应代码的字节码。例如:


const vm = require('vm');

const CODE = 'console.log("Hello world");'; // 源代码

const script = new vm.Script(CODE, {

  produceCachedData: true

});

const bytecodeBuffer = script.cachedData; // 字节码

并且,这段字节码可以脱离源代码运行:


const anotherScript = new vm.Script(' '.repeat(CODE.length), {

  cachedData: bytecodeBuffer

});

anotherScript.runInThisContext(); // 'Hello world'

这段代码看起来不那么容易理解,主要体现在创建 vm.Script 实例时传入的第一个参数:


既然源代码的字节码已经在 bytecodeBuffer 中,为何还要传入第一个参数?

为何传入与源代码长度相同的空格?

首先,创建 vm.Script 实例时,V8 会检查字节码(cachedData)是否与源代码(第一个参数传入的代码)匹配,所以第一个参数不能省略。其次,这个检查非常简单,它只会对比代码长度是否一致,所以只要使用与源代码长度相同的空格,就可以“欺骗”这个检查。


细心的读者会发现,这样一来,其实字节码并没有完全脱离源代码运行,因为需要用到源代码长度这项数据。而实际上,还有其他方法可以解决这个问题。试想一下,既然有源代码长度检查,那就说明字节码中也必然保存着源代码的长度信息,否则就无法对比了。通过查阅 V8 的相关代码,可以发现字节码的头部保存着这些信息:


// The data header consists of uint32_t-sized entries:

// [0] magic number and (internally provided) external reference count

// [1] version hash

// [2] source hash

// [3] cpu features

// [4] flag hash

其中第 [2] 项 source hash 就是源代码长度。但因为 Node.js 的 buffer 是 Uint8Array 类型的数组,所以 uint32 数组中的 [2],相当于 uint8 数组中的 [8, 9, 10, 11]。

1.png



Uint8Array and Uint32Array


接着把上述位置的数据提取出来:


const lengthBytes = bytecodeBuffer.slice(8, 12);

其结果类似于:


<Buffer 1b 00 00 00>

这是一种叫做 Little-Endian 的字节序,低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。


1.jpg


<Buffer 1b 00 00 00> 即为 0x0000001b,也就是十进制的 27。计算方法如下:


firstByte + (secondByte * 256) + (thirdByte * 256**2) + (forthByte * 256**3)

写成代码如下:


const length = lengthBytes.reduce((sum, number, power) => {

  return sum += number * Math.pow(256, power);

}, 0); // 27

此外,还有一种更简单的方法:


const length = bytecodeBuffer.readIntLE(8, 4); // 27

综上所述,运行字节码的代码可以优化为:


const length = bytecodeBuffer.readIntLE(8, 4);

const anotherScript = new vm.Script(' '.repeat(length), {

  cachedData: bytecodeBuffer

});

anotherScript.runInThisContext();

编译文件

讲清楚原理之后,下面就尝试编译一个很简单的项目,目录结构如下:


src/

lib.js

index.js

dist/

compile.js

src 目录内的两个文件为源代码,内容分别为:


// lib.js

console.log('I am lib');

exports.add = function(a, b) {

  return a + b;

};

// index.js

console.log('I am index');

const lib = require('./lib');

console.log(lib.add(1, 2));

dist 目录用于放置编译后的代码。compile.js 即为执行编译操作的文件,其流程也非常简单,读取源文件内容,编译为字节码后保存为文件(dist/*.jsc):


const path = require('path');

const fs = require('fs');

const vm = require('vm');

const glob = require('glob'); // 第三方依赖包

 

const srcPath = path.resolve(__dirname, './src');

const destPath = path.resolve(__dirname, './dist');

 

glob.sync('**/*.js', { cwd: srcPath }).forEach((filePath) => {

  const fullPath = path.join(srcPath, filePath);

  const code = fs.readFileSync(fullPath, 'utf8');

  const script = new vm.Script(code, {

    produceCachedData: true

  });

  fs.writeFileSync(

    path.join(destPath, filePath).replace(/\.js$/, '.jsc'),

    script.cachedData

  );

});

运行 node compile 后,就可以在 dist 目录内生成源代码对应的字节码文件,接下来就是运行字节码文件。然而,直接执行 node index.jsc 是无法运行的,因为 Node.js 在默认情况下会把目标文件当做 JavaScript 源代码来执行。


此时,就需要对 jsc 文件使用特殊的加载逻辑。在 dist 目录内新建文件 main.js,内容如下:


const Module = require('module');

const path = require('path');

const fs = require('fs');

const vm = require('vm');

 

// 加载 jsc 文件的扩展

Module._extensions['.jsc'] = function(module, filename) {

  const bytecodeBuffer = fs.readFileSync(filename);

  const length = bytecodeBuffer.readIntLE(8, 4);

  const script = new vm.Script(' '.repeat(length), {

    cachedData: bytecodeBuffer

  });

  script.runInThisContext();

};

 

// 调用字节码文件

require('./index');

执行 node dist/main,虽然 jsc 文件可以加载进来了,但是就出现了另一段异常信息:


ReferenceError: require is not defined

这是个奇怪的问题,在 Node.js 中,require 是个很基础的函数,怎么会未定义呢?原来,Node.js 在编译 js 文件的过程中会对其内容进行包装。以 index.js 为例,包装后的代码如下:


(function (exports, require, module, __filename, __dirname) {

  console.log('I am index');

  const lib = require('./lib');

  console.log(lib.add(1, 2));

});

包装这个操作并不在编译字节码这个步骤里面,而是在之前执行。所以,要在 compile.js 补上包装(Module.wrap)操作:


const script = new vm.Script(Module.wrap(code), {

  produceCachedData: true

});

加上包装之后,script.runInThisContext 就会返回一个函数,执行这个函数才能运行模块,修改代码如下:


Module._extensions['.jsc'] = function(module, filename) {

  // 省略 N 行代码

 

  const compiledWrapper = script.runInThisContext();

  return compiledWrapper.apply(module.exports, [

    module.exports,

    id => module.require(id),

    module,

    filename,

    path.dirname(filename),

    process,

    global

  ]);

};

再次执行 node dist/main.js,出现了另一条错误信息:


SyntaxError: Unexpected end of input

这是一个让人一脸懵逼,不知道从何查起的错误。但是,仔细观察控制台又可以发现,在错误信息之前,两条日志已经打印出来了:


I am index

I am lib

由此可见,错误信息是执行 lib.add 时产生的。所以,结论就是,函数以外的逻辑可以正常执行,函数内部的逻辑执行失败。


回想 V8 编译的流程。它解析 JavaScript 代码的过程中,Toplevel 部分会被解释器完全解析,生成抽象语法树以及字节码。Non Toplevel 部分仅仅被预解析(语法检查),不会生成语法树,更不会生成字节码。Non Toplevel 部分,即函数体部分,只有在函数被调用的时候才会被编译。


所以问题也就一目了然了:函数体没有编译成字节码。幸好,这种行为也是可以更改的:


const v8 = require('v8');

v8.setFlagsFromString('--no-lazy');

设置了 no-lazy 标志后再执行 node compile 进行编译,函数体也可以被完全解析了。最终 compile.js 代码如下:


const path = require('path');

const fs = require('fs');

const vm = require('vm');

const Module = require('module');

const glob = require('glob');

const v8 = require('v8');

v8.setFlagsFromString('--no-lazy');

 

const srcPath = path.resolve(__dirname, './src');

const destPath = path.resolve(__dirname, './dist');

 

glob.sync('**/*.js', { cwd: srcPath }).forEach((filePath) => {

  const fullPath = path.join(srcPath, filePath);

  const code = fs.readFileSync(fullPath, 'utf8');

  const script = new vm.Script(Module.wrap(code), {

    produceCachedData: true

  });

  fs.writeFileSync(

    path.join(destPath, filePath).replace(/\.js$/, '.jsc'),

    script.cachedData

  );

});

dist/main.js 代码如下:


const Module = require('module');

const path = require('path');

const fs = require('fs');

const vm = require('vm');

const v8 = require('v8');

v8.setFlagsFromString('--no-lazy');

 

Module._extensions['.jsc'] = function(module, filename) {

  const bytecodeBuffer = fs.readFileSync(filename);

  const length = bytecodeBuffer.readIntLE(8, 4);

  const script = new vm.Script(' '.repeat(length), {

    cachedData: bytecodeBuffer

  });

 

  const compiledWrapper = script.runInThisContext();

  return compiledWrapper.apply(module.exports, [

    module.exports,

    id => module.require(id),

    module,

    filename,

    path.dirname(filename),

    process,

    global

  ]);

};

 

require('./index');

bytenode

实际上,如果你真的需要把 JavaScript 源代码编译成字节码,并不需要自己去编写这么多的代码。npm 平台上已经有一个叫做 bytenode 的包可以完成这些事情,并且它在细节和兼容性上做得更好。


字节码的问题

虽然编译成字节码后可以保护源代码,但字节码也会存在一些问题:


JavaScript 源代码可以在任何平台的 Node.js 环境中运行,但字节码是平台相关的,在何种平台下编译,就只能在何种平台下运行(比如在 Windows 下编译的字节码不能在 macOS 下运行)。

修改源代码后要再次编译为字节码,较为繁琐。对于一些如数据库服务器地址、端口号等配置信息,建议不要编译成字节码,仍使用源文件运行,方便随时修改。

后记

作为一名聪明的读者,你必定能猜到,本文是以倒叙的方式写的。笔者是先使用 bytenode 完成了需求,再研究其原理。



NodeJs 源码保护

现在 NodeJs 开发 Server 端越来越流行,如果 Server 部署在自己公司的服务器上,那么可以认为环境是相对安全的,不需要做源码保护。但是如果需要在客户方部署,又不希望自己的源码暴露的时候,这个时候就需要源码保护。一般的源码保护方式就是 js 压缩/混淆之类的操作,增加 js 代码的不可读性,或者说是增加破解难度。


        本文讨论另一种使用字节码编译 nodejs 代码来保护源码的方式。


        NodeJs 使用 google 的 V8 引擎进行编译,具体可以参考  https://zhuanlan.zhihu.com/p/28590489,同时,我们利用 bytenode 这个插件来辅助生成字节码文件,具体请参考 https://github.com/OsamaAbbas/bytenode。


        项目构成:使用 express 创建一个项目,项目目录如下:


        1.png


        主要文件及说明:


        bin/www:程序主入口


        routes/:路由 js 文件


        services/:核心业务逻辑处理的 js 文件


        app.js:NodeJs Server 启动入口。


        compile.js:字节码编译 js 的文件,后面会讲到。


        其余的文件不重要,也不会被编译成字节码。


        核心思路就是将关键的 js 代码编译成字节码,以保护我们的业务处理逻辑或者算法。


1. 安装依赖


npm install bytenode --save


2. compile.js,主要逻辑就是将项目代码拷贝到 dist 目录中,遍历 dist 下 routes 和 services 等核心 js 文件目录,使用 bytenode 插件将所有的 js 转换成 jsc 字节码文件,然后删除 js 源文件。


var bytenode = require('bytenode');

var fs = require('fs');

var path = require("path");


fs.exists('./dist', exist => {

    if (exist) {

        delDir('./dist');

    }

    fs.mkdirSync('./dist');

})


// 拷贝目录到 dist 下

fs.readdir('./', (err, files) => {

    if (err) {

        console.error(err);

        return;

    }

    for (var i = 0; i < files.length; i++) {

        var stat = fs.statSync('./' + files[i]);

        if (stat.isFile()) {

            if (files[i].indexOf('compile.js') == -1) {

                fs.writeFileSync('./dist/' + files[i], fs.readFileSync('./' + files[i]));

            }

        } else if (stat.isDirectory() && files[i].indexOf('dist') == -1) {

            createDocs('./' + files[i], './dist/' + files[i], function () {


            })

        } else {


        }

    }


    compileFile()

})


function compileFile() {

    // 编译 app.js 为字节码

    bytenode.compileFile({

        filename: './dist/app.js'

    });

    fs.unlinkSync('./dist/app.js');


    // 编译 filters/routes/services 目录下的js文件为字节码

    compileDir('./dist/filters');

    compileDir('./dist/routes');

    compileDir('./dist/services');

}


function compileDir(dir) {

    var stat = fs.statSync(dir);

    if (stat.isFile() && dir.indexOf('.js') != -1) {

        // 文件,直接转换

        bytenode.compileFile({

            filename: dir

        });

        fs.unlinkSync(dir);

    } else if (stat.isDirectory()) {

        // 目录,列出文件列表,循环处理

        var files = fs.readdirSync(dir);

        for (var i = 0; i < files.length; i++) {

            var file = dir + '/' + files[i];

            compileDir(file);

        }

    } else {


    }

}


//递归创建目录 同步方法  

function mkdirsSync(dirname) {

    if (fs.existsSync(dirname)) {

        return true;

    } else {

        if (mkdirsSync(path.dirname(dirname))) {

            console.log("mkdirsSync = " + dirname);

            fs.mkdirSync(dirname);

            return true;

        }

    }

}


function _copy(src, dist) {

    var paths = fs.readdirSync(src)

    paths.forEach(function (p) {

        var _src = src + '/' + p;

        var _dist = dist + '/' + p;

        var stat = fs.statSync(_src)

        if (stat.isFile()) {// 判断是文件还是目录

            fs.writeFileSync(_dist, fs.readFileSync(_src));

        } else if (stat.isDirectory()) {

            copyDir(_src, _dist)// 当是目录是,递归复制

        }

    })

}


/*

 * 复制目录、子目录,及其中的文件

 * @param src {String} 要复制的目录

 * @param dist {String} 复制到目标目录

 */

function copyDir(src, dist) {

    var b = fs.existsSync(dist)

    console.log("dist = " + dist)

    if (!b) {

        console.log("mk dist = ", dist)

        mkdirsSync(dist);//创建目录

    }

    console.log("_copy start")

    _copy(src, dist);

}


function createDocs(src, dist, callback) {

    console.log("createDocs...")

    copyDir(src, dist);

    console.log("copyDir finish exec callback")

    if (callback) {

        callback();

    }

}


function delDir(path) {

    let files = [];

    if (fs.existsSync(path)) {

        files = fs.readdirSync(path);

        files.forEach((file, index) => {

            let curPath = path + "/" + file;

            if (fs.statSync(curPath).isDirectory()) {

                delDir(curPath); //递归删除文件夹

            } else {

                fs.unlinkSync(curPath); //删除文件

            }

        });

        fs.rmdirSync(path);

    }

}


3. 修改 bin/www 文件,在最开始 引入 bytenode


require('bytenode');

var app = require('../app');

var debug = require('debug')('esreader-server:server');

var http = require('http');


...


4. 执行指令打包编译。


node compile.js

编译完成之后, dist 下面的所有文件即可作为发布到第三方服务器上的server。


这样做完之后,项目的启动,或者使用诸如 pm2 等工具来管理 server 时,都与之前的固有做法一致,不需要特殊处理。



JS生成字节码生成技术,用字节码保护商业NodeJS源码!

本文介绍一种NodeJS源代码保护方式:通过把nodejs代码转化为字节码,用node启动字节码文件的方式,保护nodejs源代码不泄漏。


可应用于nodejs项目提交源码、nodejs产品在不可信的环境中部署,防止别人获取源码。


如同JS代码一样,nodejs源码,也是透明代码,通常用node启动代码时,都必须把源码也放置到启动环境中。这在很多时候是不安全不稳妥的。因为js源码透明的原因,别人可以直接获取到产品或项目源码。

如果是为第三方定制项目,对方可以直接拿到源码。如果是要在某些环境中启动项目,比如虚拟主机、他人的服务器中,源码的也是很令人担心的。


为了防止源码泄漏带来的一系列令人不安的后果,这里介绍一种专门针对于nodejs源码的保护技术:将nodejs代码转化为字节码文件。



实现原理


nodejs的内核中对于js的解析,使用的是谷歌的v8引擎。v8引擎内置有js虚拟机。通过v8虚拟机,可以将js代码编译为字节码。而v8虚拟机是能够识别和直接运行该字节码的。因此,以下执行逻辑成为可能:


1、js代码 -> js字节码

2、js字节码 -> nodejs ->运行



实现代码

(例程)


生成字节码文件的部分:


var v8 = require('v8');

var fs = require('fs');


//读取源文件(JS源码)

var js_code = fs.readFileSync(__dirname+"/test.js").toString();


//生成字节码

var script = new vm.Script(js_code, {produceCachedData: true});

var byte_code = script.cachedData;


//将字节码写入文件

fs.writeFileSync(__dirname+"/test.jsb",byte_code);



读取并运行字节码的部分:



var v8 = require('v8');

var fs = require('fs');


//从文件中读取字节码

byte_code = fs.readFileSync(__dirname+"/test.jsb");


//运行

var l = byte_code.slice(8, 12).reduce(function (sum, number, power) { return sum += number * Math.pow(256, power);});

var dummyCode =" ".repeat(l);

script = new vm.Script(dummyCode, {cachedData: byte_code});

script.runInThisContext();



运行效果


test.js是本例的测试文件,内容如下:


console.log("hello world");

console.log("this is a test");


运行效果如下:


1.png



生成字节码,读取、运行字节码。如此操作起来,并不复杂,如果量大的话,还是稍有些繁琐的。


另外一个弊端是:兼容性问题。比如win下生成的字节码,到linux下,是不能正常运行的。如果要在linux下用,就要在linux下生成。也就是操作系统要一致。


对于JS代码产品的保护,除了可以使用字节码技术,还可以用代码混淆加密的办法,比如:JShaman(http://www.jshaman.com/)是一款对JS代码进行混淆加密的工具,也适用于nodejs代码加密,也是个非常不错的nodejs代码保护手段,且可对前端JS代码进行保护,通用性比较强。


nodejs代码加密

https://www.npmjs.com/package/dir-compress

how to use

npm install dir-compress

var compresser = require('dir-compress');

compresser.compress({

    rootPath: 'folder want to be compressed',

    newRootPath: 'new dir root',

    exclude: ['/folder'] //this path is relative to rootPath, which will not be compressed

})














上一篇:实用代码下载

下一篇:NodeJS目录结构

Top