node-module-concat
Advanced tools
Comparing version 1.5.0 to 2.0.0-beta
311
index.js
/* node-module-concat | ||
Node.js module concatenation library | ||
This library exposes a single function that concatenates Node.js modules | ||
within a project. This can be used to obfuscate an entire project into a | ||
single file. It can also be used to write client-side JavaScript code where | ||
each file is written just like a Node.js module. | ||
Known limitations: | ||
- Dynamic `require()` statements don't work | ||
(i.e. `require("./" + variable)`) | ||
- `require.resolve` calls are not modified | ||
- `require.cache` statements are not modified | ||
CommonJS module concatenation library | ||
*/ | ||
const fs = require("fs"), | ||
ModuleConcatStream = require("./lib/moduleConcatStream"); | ||
var fs = require("fs") | ||
, path = require("path") | ||
, stride = require("stride"); | ||
// Read main header/footer and header/footer for each file in the project | ||
var header = fs.readFileSync(__dirname + "/lib/header.js").toString("utf8") | ||
, footer = fs.readFileSync(__dirname + "/lib/footer.js").toString("utf8") | ||
, fileHeader = fs.readFileSync(__dirname + "/lib/fileHeader.js").toString("utf8") | ||
, fileFooter = fs.readFileSync(__dirname + "/lib/fileFooter.js").toString("utf8"); | ||
/* Concatenate all modules within a project. | ||
The procedure works like this: | ||
0.) A special header file is added to the `outputFile`. See | ||
`./lib/header.js` for details. | ||
1.) Read the entry module, as specified by its path. | ||
2.) Scan the file for `require("...")` or `require('...')` statements. | ||
Note: Dynamic require statements such as `require("./" + b)` are not | ||
matched. | ||
3.) If the fixed path specified by the `require("...")` statement is a | ||
relative or absolute filesystem path (i.e. it begins with "./", "../", | ||
or "/"), then that file is added to the project and recursively scanned | ||
for `require` statements as in step #2. Additionally, the file is given | ||
an unique identifier, and the `require("...")` statement is replaced | ||
with `__require(id)` where `id` is the unique identifier. `__require` | ||
is explained in more detail in `./lib/header.js`. | ||
4.) In addition, any reference to `__dirname` or `__filename` is replaced | ||
with `__getDirname(...)` and `__getFilename(...)`, which are explained | ||
in `./lib/header.js`. | ||
Note: __dirname and __filename references are replaced with paths | ||
relative to the `outputFile` at the time of concatenation. | ||
Therefore, if you move `outputFile` to a different path, the | ||
__dirname and __filename reference will also change, but it will | ||
still be the relative path at the time of concatenation. If you | ||
don't know what I mean and you are having issues, please just read | ||
./lib/header.js and look at the contents of the `outputFile`. | ||
5.) Finally, the modified file is wrapped with a header and footer to | ||
encapsulate the module within its own function. The wrapped code is | ||
then written to the `outputFile`. | ||
6.) Once all of the modules are written to the `outputFile`, a footer is | ||
added to the `outputFile` telling Node to require and execute the entry | ||
module. | ||
Any source file added to the project has: | ||
- A prepended header (./lib/fileHeader.js) | ||
- An appended footer (./lib/fileFooter.js) | ||
- Certain `require` statements replaced with `__require` | ||
- All `__dirname` and `__filename` references replaced with | ||
`__getDirname(...)` and `__getFilename(...)` references. | ||
`modConcat(entryModule, outputFile, [options,] cb)` | ||
- `entryModule` - the path to the entry point of the project to be | ||
concatenated. This might be an `index.js` file, for example. | ||
- `outputFile` - the path where the concatenated project file will be | ||
written. | ||
- `options` - Optional. An Object containing any of the following: | ||
- `outputStreamOptions` - Options passed to `fs.createWriteStream` call | ||
when the `outputFile` is opened for writing. Defaults to `null`. | ||
- `excludeFiles` - An Array of files that should be excluded from the | ||
project even if they were referenced by a `require(...)`. | ||
Note: These `require` statements should probably be wrapped in a | ||
try, catch block to prevent uncaught exceptions. | ||
- `includeNodeModules` - Set to `true` if node_modules should be | ||
included in the `outputFile` | ||
- `cb` - Callback of the form `cb(err, files)` where `files` is an Array | ||
of files that have been included in the project. | ||
*/ | ||
module.exports = function concat(entryModule, outputFile, opts, cb) { | ||
// Reorganize arguments | ||
function modConcat(entryModule, outputPath, opts, cb) { | ||
// Re-organize arguments | ||
if(typeof opts === "function") { | ||
@@ -92,199 +13,51 @@ cb = opts; | ||
} | ||
opts = opts || {}; | ||
// Ensure that all paths have been resolved | ||
if(opts.excludeFiles) { | ||
for(var i = 0; i < opts.excludeFiles.length; i++) { | ||
opts.excludeFiles[i] = path.resolve(opts.excludeFiles[i]); | ||
} | ||
} | ||
// Ensure that `includeNodeModules` will work properly | ||
var _nodeModulePaths; | ||
if(opts.includeNodeModules) { | ||
try { | ||
_nodeModulePaths = require("module")._nodeModulePaths; | ||
if(typeof _nodeModulePaths !== "function") { | ||
opts.includeNodeModules = false; | ||
// Create a Promise that resolves once the concatenation is complete | ||
let p = new Promise((resolve, reject) => { | ||
opts = opts || {}; | ||
opts.outputPath = outputPath; | ||
// Create output stream and ModuleConcatStream | ||
let out = fs.createWriteStream(outputPath); | ||
out.on("error", reject); | ||
let project = new ModuleConcatStream(entryModule, opts); | ||
project.on("error", reject); | ||
// Pipe to outputPath and resolve to stats when complete | ||
out.on("finish", () => { | ||
try { | ||
resolve(project.getStats() ) | ||
} catch(e) { | ||
reject(e); | ||
} | ||
} catch(err) { | ||
opts.includeNodeModules = false; | ||
} | ||
} | ||
// A list of all of the files read and included in the output thus far | ||
var files = []; | ||
// A list of all of the native modules not included in the output thus far | ||
var nativeModules = []; | ||
// The file descriptor pointing to the `outputFile` | ||
var fd; | ||
stride(function writeMainHeader() { | ||
var cb = this; | ||
// Open WriteStream | ||
var out = fs.createWriteStream(outputFile, opts.outputStreamOptions); | ||
out.on("open", function(_fd) { | ||
// Save the file descriptor | ||
fd = _fd; | ||
// Write main header | ||
fs.write(fd, header, null, "utf8", cb); | ||
}); | ||
}, function processEntryModule() { | ||
// Add entry module to the project | ||
files.push(path.resolve(entryModule) ); | ||
addToProject(fd, files[0], this); | ||
}, function writeMainFooter() { | ||
// Write main footer | ||
fs.write(fd, footer, null, "utf8", this); | ||
}).once("done", function(err) { | ||
if(fd) { | ||
fs.close(fd, function(closeErr) { | ||
cb(err || closeErr, files, nativeModules); | ||
}); | ||
} else { | ||
cb(err, files, nativeModules); | ||
} | ||
project.pipe(out); | ||
setTimeout(() => project.pause(), 1); | ||
}); | ||
function getPathRelativeToOutput(filePath) { | ||
return path.relative(path.dirname(outputFile), filePath); | ||
// Call `cb` if it was provided; otherwise return the Promise | ||
if(typeof cb === "function") { | ||
p.then((data) => { | ||
process.nextTick(() => cb(null, data) ); | ||
}).catch((err) => { | ||
process.nextTick(() => cb(err) ); | ||
}); | ||
} else { | ||
return p; | ||
} | ||
function addToProject(fd, filePath, cb) { | ||
// Keep track of the current `files` length; we need it later | ||
var lengthBefore = files.length; | ||
stride(function writeHeader() { | ||
// Write module header | ||
fs.write(fd, fileHeader | ||
.replace(/\$\{id\}/g, files.indexOf(filePath) ) | ||
.replace(/\$\{path\}/g, filePath), null, "utf8", this); | ||
}, function writeExtraHeaderForJSON() { | ||
// If this is a *.json file, add some extra fluff | ||
if(path.extname(filePath) === ".json") | ||
fs.write(fd, "module.exports = ", null, "utf8", this); | ||
else | ||
this(null); | ||
}, function readFile() { | ||
// Read file | ||
fs.readFile(filePath, {"encoding": "utf8"}, this); | ||
}, function processFile(code) { | ||
// Remove some line comments from code | ||
code = code.replace(/(?:\r\n?|\n)\s*\/\/.*/g, ""); | ||
// Scan file for `require(...)`, `__dirname`, and `__filename` | ||
/* Quick notes about the somewhat intense `requireRegex`: | ||
- require('...') and require("...") is matched | ||
- The single or double quote matched is group 1 | ||
- Whitespace can go anywhere | ||
- The module path matched is group 2 | ||
- Backslashes are allowed as escape characters only if followed | ||
by another backlash (to support Windows paths) | ||
*/ | ||
var requireRegex = /require\s*\(\s*(["'])((?:(?:(?!\1)[^\\]|(?:\\\\)))*)\1\s*\)/g, | ||
dirnameRegex = /__dirname/g, | ||
filenameRegex = /__filename/g; | ||
code = code.replace(requireRegex, function(match, quote, modulePath) { | ||
// First thing is to replace "\\" with "\" | ||
modulePath = modulePath.replace("\\\\", "\\"); | ||
// Check to see if this require path begins with "./" or "../" or "/" | ||
if(modulePath.match(/^\.?\.?\//) !== null) { | ||
try { | ||
modulePath = require.resolve(path.resolve( | ||
path.join(path.dirname(filePath), modulePath) | ||
) ); | ||
// Include module in project at end of this function | ||
} catch(e) { | ||
// Ignore; do not replace | ||
return match; | ||
} | ||
} else if(opts.includeNodeModules) { | ||
var oldPaths = module.paths; | ||
/* Temporarily overwrite `module.paths` to make | ||
`require.resolve` work properly */ | ||
module.paths = _nodeModulePaths(path.dirname(filePath) ); | ||
try { | ||
var modulePath = require.resolve(modulePath); | ||
} catch(err) { | ||
// Module not found; do not replace | ||
return match; | ||
} finally { | ||
// Restore old `module.paths` | ||
module.paths = oldPaths; | ||
} | ||
// Detect core module | ||
if(modulePath.match(/^[a-z_]+$/) ) { | ||
// Core module; do not replace | ||
return match; | ||
} | ||
// Include module in project at end of this function | ||
} else { | ||
// Ignore; do not replace | ||
return match; | ||
} | ||
/* If we reached this point, we need to include `modulePath` | ||
in our project */ | ||
// If this is a native module, abort | ||
if(path.extname(modulePath).toLowerCase() === ".node") { | ||
// This is a native module; do not replace | ||
nativeModules.push(modulePath); | ||
return match; | ||
} | ||
// Lookup this module's ID | ||
var index = files.indexOf(modulePath); | ||
if(index < 0) { | ||
// Not found; add this module to the project | ||
if(!opts.excludeFiles || | ||
opts.excludeFiles.indexOf(modulePath) < 0) | ||
{ | ||
index = files.push(modulePath) - 1; | ||
} | ||
else { | ||
// Ignore; do not replace | ||
return match; | ||
} | ||
} | ||
// Replace the `require` statement with `__require` | ||
var parentIndex = files.indexOf(filePath); | ||
return "__require(" + index + "," + parentIndex + ")"; | ||
}) | ||
// Replace `__dirname` with `__getDirname(...)` | ||
.replace(dirnameRegex, "__getDirname(" + | ||
JSON.stringify(getPathRelativeToOutput(filePath) ) + ")") | ||
// Replace `__filename` with `__getFilename(...)` | ||
.replace(filenameRegex, "__getFilename(" + | ||
JSON.stringify(getPathRelativeToOutput(filePath) ) + ")"); | ||
// Write the modified code | ||
fs.write(fd, code, null, "utf8", this); | ||
}, function writeFooter() { | ||
// Write module footer | ||
index = files.indexOf(filePath); | ||
fs.write(fd, fileFooter | ||
.replace(/\$\{id\}/g, index) | ||
.replace(/\$\{path\}/g, filePath), null, "utf8", this); | ||
}, function addPendingFiles() { | ||
// Process any pending files that were required by this file | ||
var lengthAfter = files.length; | ||
var args = []; | ||
for(var i = lengthBefore; i < lengthAfter; i++) { | ||
// Create new context for filename | ||
(function(filename) { | ||
args.push(function() { | ||
addToProject(fd, filename, this); | ||
}); | ||
})(files[i]); | ||
} | ||
// Pass the `args` Array to stride, and kick off the processing | ||
stride.apply(null, args).once("done", this); | ||
}).once("done", cb); | ||
} | ||
}; | ||
// If this module is invoked directly, behave like a cli | ||
// Expose `modConcat` function and `ModuleConcatStream` | ||
modConcat.ModuleConcatStream = ModuleConcatStream; | ||
module.exports = modConcat; | ||
// If this module is invoked directly, behave like a CLI | ||
if(require.main === module) { | ||
if(process.argv.length !== 4) { | ||
console.log("Usage: node concat.js [entryModule] [outputFile]"); | ||
process.exit(1); | ||
console.log("Usage: node concat.js [entryModule] [outputPath]"); | ||
return process.exit(1); | ||
} | ||
module.exports(process.argv[2], process.argv[3], function(err, files) { | ||
modConcat(process.argv[2], process.argv[3], function(err, stats) { | ||
if(err) { | ||
console.error("Error", err.stack); | ||
process.exit(1); | ||
} | ||
else { | ||
console.log(files.length + " files written to " + process.argv[3] + "."); | ||
} else { | ||
console.log(stats.files.length + " files written to " + | ||
process.argv[3] + "."); | ||
console.log("Completed successfully."); | ||
@@ -291,0 +64,0 @@ } |
@@ -20,3 +20,3 @@ /* This header is placed at the beginning of the output file and defines the | ||
__moduleIsCached[uid] = true; | ||
if(uid === 0 && typeof require === "object") { | ||
if(uid === 0 && typeof require === "function") { | ||
require.main = __modulesCache[0]; | ||
@@ -23,0 +23,0 @@ } else { |
{ | ||
"name": "node-module-concat", | ||
"version": "1.5.0", | ||
"description": "Node.js module concatenation library", | ||
"version": "2.0.0-beta", | ||
"description": "CommonJS module concatenation library", | ||
"main": "index.js", | ||
"dependencies": { | ||
"stride": ">=0.1.4 <2" | ||
"resolve": ">=1.2 <2" | ||
}, | ||
@@ -20,2 +20,3 @@ "devDependencies": {}, | ||
"module", | ||
"commonjs", | ||
"concatenate", | ||
@@ -26,2 +27,3 @@ "obfuscation", | ||
], | ||
"engines": {"node" : ">=4"}, | ||
"author": "Blake Miner <miner.blake@gmail.com>", | ||
@@ -28,0 +30,0 @@ "license": "MIT", |
# node-module-concat | ||
Node.js module concatenation library | ||
CommonJS module concatenation library | ||
## What is it? | ||
This library exposes a single function that concatenates Node.js modules | ||
This library exposes a single function that concatenates CommonJS modules | ||
within a project. This can be used to obfuscate an entire project into a | ||
@@ -29,4 +29,44 @@ single file. It can also be used to write client-side JavaScript code where | ||
**`modConcat(entryModule, outputFile, [options,] cb)`** | ||
**`var stream = new modConcat.ModuleConcatStream(entryModulePath [, options])`** | ||
Constructs a [Readable Stream](https://nodejs.org/api/stream.html#stream_class_stream_readable) | ||
of the concatenated project. | ||
- `entryModulePath` - the path to the entry point of the project to be | ||
concatenated. This might be an `index.js` file, for example. | ||
- `options` - object to specify any of the following options | ||
- `outputPath` - the path where the concatenated project file will be | ||
written. Provide this whenever possible to ensure that instances | ||
of `__dirname` and `__filename` are replaced properly. If | ||
`__dirname` and `__filename` are not used in your project or your | ||
project dependencies, it is not necessary to provide this path. This | ||
has no effect when `browser` option is set. | ||
- `excludeFiles` - An Array of files that should be excluded from the | ||
project even if they were referenced by a `require(...)`. | ||
Note: These `require` statements should probably be wrapped with a | ||
conditional or a try/catch block to prevent uncaught exceptions. | ||
- `excludeNodeModules` - Set to `true` if modules loaded from | ||
`node_modules` folders should be excluded from the project. | ||
- `browser` - Set to `true` when concatenating this project for the | ||
browser. In this case, whenever a required library is loaded from | ||
`node_modules`, the `browser` field in the `package.json` file (if | ||
found) is used to determine which file to actually include in the | ||
project. | ||
- Any [option supported by the Readable class] | ||
(https://nodejs.org/api/stream.html#stream_new_stream_readable_options) | ||
**`stream.getStats()`** | ||
Returns an Object containing statistics about the files included in the | ||
project. This object is available after the 'end' event is fired and there | ||
is no more data to consume. Properties include: | ||
- `files` - An Array of files included in the project | ||
- `addonsExcluded` - An Array of files excluded from the project because | ||
they are native C/C++ add-ons. | ||
**`modConcat(entryModule, outputPath, [options, cb])`** | ||
Helper function that constructs a new `ModuleConcatStream` (see above) with | ||
the following options and pipes the concatenated project to the `outputPath`. | ||
- `entryModule` - the path to the entry point of the project to be | ||
@@ -36,17 +76,7 @@ concatenated. This might be an `index.js` file, for example. | ||
written. | ||
- `options` - Optional. An Object containing any of the following: | ||
- `outputStreamOptions` - Options passed to `fs.createWriteStream` call | ||
when the `outputFile` is opened for writing. | ||
- `excludeFiles` - An Array of files that should be excluded from the | ||
project even if they were referenced by a `require(...)`. | ||
- `options` - See `options` for `ModuleConcatStream` above. | ||
- `cb` - Callback of the form `cb(err, stats)`. If no callback is provided, | ||
a Promise is returned instead, which resolves to the `stats` Object returned | ||
by `stream.getStats()` (see above). | ||
Note: These `require` statements should probably be wrapped in a | ||
try, catch block to prevent uncaught exceptions. | ||
- `includeNodeModules` - Set to `true` if node_modules should also be | ||
included in the project. | ||
- `cb` - Callback of the form `cb(err, files, nativeModules)` where `files` is | ||
an Array of files that have been included in the project and | ||
`nativeModules` is an Array of native modules that were found (but not | ||
included) when scanning the project. | ||
## Known limitations | ||
@@ -53,0 +83,0 @@ - Dynamic `require()` statements don't work |
var fs = require("fs") | ||
, hello = require("./lib/hello") | ||
, world = require("./lib/world") | ||
, func = require("./lib/func"); | ||
, func = require("./lib/func") | ||
, cool = require("cool"); | ||
try { | ||
let foo = require("notfound"); | ||
} catch(e) {} | ||
console.log(hello.hello + ", " + world.world); | ||
@@ -18,1 +23,2 @@ console.log(fs.readFileSync(__filename).toString() ); | ||
} | ||
console.log(cool); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
21425
18
421
85
4
2
1
+ Addedresolve@>=1.2 <2
+ Addedfunction-bind@1.1.2(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedis-core-module@2.15.1(transitive)
+ Addedpath-parse@1.0.7(transitive)
+ Addedresolve@1.22.8(transitive)
+ Addedsupports-preserve-symlinks-flag@1.0.0(transitive)
- Removedstride@>=0.1.4 <2
- Removedstride@1.0.0(transitive)