Skip to content

Commit c76788c

Browse files
committed
Added options.extensions and options.compilers
`options` are now passed along to `resolve` module Updated README documentation to explain `extensions` and `compilers` options Added CHANGELOG Push to 2.0.0
1 parent 17033e5 commit c76788c

File tree

5 files changed

+127
-62
lines changed

5 files changed

+127
-62
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# 2.0.0
2+
3+
Almost a complete re-write of the original project.
4+
5+
- Supports streaming CommonJS modules to a Readable stream
6+
- Supports bundling for the browser, Node.js, and others
7+
- Added Promise support
8+
- Dropped support for Node < 4
9+
- Changed `options` a bit (i.e. `excludeNodeModules` instead of
10+
`includeNodeModules`)
11+
- Added `options.extensions` and `options.compilers` (see README)
12+
- Changed format of `cb`: now `cb(err, stats)` where `stats` exposes files
13+
included / excluded in the project build
14+
- Now depends on NPM `resolve` package
15+
- Fixed a few bugs
16+
- Updated docs and added a bit more complexity to the test_project
17+
18+
# 1.x
19+
20+
It's old now, and I'm too lazy to fill in the changelog. :)

README.md

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
# node-module-concat
2-
CommonJS module concatenation library
2+
Fairly lightweight CommonJS module concatenation tool
33

44
## What is it?
5-
This library exposes a single function that concatenates CommonJS modules
6-
within a project. This can be used to obfuscate an entire project into a
7-
single file. It can also be used to write client-side JavaScript code where
5+
This library exposes a single function and stream API that concatenates CommonJS
6+
modules within a project. This can be used to obfuscate an entire project into
7+
a single file. It can also be used to write client-side JavaScript code where
88
each file is written just like a Node.js module.
99

10+
## Why?
11+
Because projects like Webpack and Browserify are cool, but they are a little
12+
heavy for my taste. I just wanted something to compile CommonJS modules into a
13+
single JavaScript file. This project has one dependency:
14+
[resolve](https://github.com/substack/node-resolve)
15+
1016
## Install
1117

1218
`npm install node-module-concat`
@@ -32,25 +38,66 @@ Constructs a [Readable Stream](https://nodejs.org/api/stream.html#stream_class_s
3238
of the concatenated project.
3339
- `entryModulePath` - the path to the entry point of the project to be
3440
concatenated. This might be an `index.js` file, for example.
35-
- `options` - object to specify any of the following options
41+
- `options` - object to specify any of the following options:
3642
- `outputPath` - the path where the concatenated project file will be
3743
written. Provide this whenever possible to ensure that instances
3844
of `__dirname` and `__filename` are replaced properly. If
3945
`__dirname` and `__filename` are not used in your project or your
4046
project dependencies, it is not necessary to provide this path. This
41-
has no effect when `browser` option is set.
47+
has no effect when the `browser` option is set.
4248
- `excludeFiles` - An Array of files that should be excluded from the
4349
project even if they were referenced by a `require(...)`.
4450

4551
Note: These `require` statements should probably be wrapped with a
4652
conditional or a try/catch block to prevent uncaught exceptions.
4753
- `excludeNodeModules` - Set to `true` if modules loaded from
4854
`node_modules` folders should be excluded from the project.
55+
- `extensions` - An Array of extensions that will be appended to the
56+
required module path to search for the module in the file system.
57+
Defaults to `[".js", ".json"]`.
58+
59+
For example, `require("./foo")` will search for:
60+
- `./foo`
61+
- `./foo.js`
62+
- `./foo.json`
63+
in that order, relative to the file containing the require statement.
64+
65+
Another example, `require("./foo.js")` will search for:
66+
- `./foo.js`
67+
- `./foo.js.js`
68+
- `./foo.js.json`
69+
70+
**Note**: ".node" file extensions are considered to be native C/C++
71+
addons and are always excluded from the build.
72+
- `compilers` - An Object describing how files with certain file extensions
73+
should be compiled to JavaScript before being included in the project.
74+
The example below will allow node-module-concat to handle `require`
75+
statements pointing to *.coffee files (i.e. `require("./foo.coffee")`).
76+
These modules are compiled using the coffee-script compiler before
77+
they are included in the project.
78+
```javascript
79+
{
80+
".coffee": (src, options) => require("coffee-script").compile(src)
81+
}
82+
```
83+
`options` are passed along to the compiler function, as shown above.
84+
85+
**Note**: By default, ".json" files are prepended with
86+
`module.exports = `. This behavior can be overwritten by explicitly
87+
specifying the ".json" key in the `compilers` Object.
88+
89+
**Note**: By default, the file extensions specified in `compilers` are
90+
not added to the `extensions` option, so `require("./foo")` will not
91+
find `./foo.coffee` unless ".coffee" is explicitly added to `extensions`
92+
(see above).
4993
- `browser` - Set to `true` when concatenating this project for the
5094
browser. In this case, whenever a required library is loaded from
5195
`node_modules`, the `browser` field in the `package.json` file (if
5296
found) is used to determine which file to actually include in the
5397
project.
98+
- Any [option supported by resolve.sync]
99+
(https://github.com/substack/node-resolve#resolvesyncid-opts) except
100+
`basedir` and `packageFilter`, which can be overwritten.
54101
- Any [option supported by the Readable class]
55102
(https://nodejs.org/api/stream.html#stream_new_stream_readable_options)
56103

lib/moduleConcatStream.js

Lines changed: 51 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -63,23 +63,12 @@ API
6363
- `entryModulePath` - the path to the entry point of the project to be
6464
concatenated. This might be an `index.js` file, for example.
6565
- `options` - object to specify any of the following options
66-
- `outputPath` - the path where the concatenated project file will be
67-
written. Provide this whenever possible to ensure that instances
68-
of `__dirname` and `__filename` are replaced properly. If
69-
`__dirname` and `__filename` are not used in your project or your
70-
project dependencies, it is not necessary to provide this path.
71-
- `excludeFiles` - An Array of files that should be excluded from the
72-
project even if they were referenced by a `require(...)`.
73-
74-
Note: These `require` statements should probably be wrapped with a
75-
conditional or a try/catch block to prevent uncaught exceptions.
76-
- `excludeNodeModules` - Set to `true` if modules loaded from
77-
`node_modules` folders should be excluded from the project.
78-
- `browser` - Set to `true` when concatenating this project for the
79-
browser. In this case, whenever a required library is loaded from
80-
`node_modules`, the `browser` field in the `package.json` file (if
81-
found) is used to determine which file to actually include in the
82-
project.
66+
- `outputPath`
67+
- `excludeFiles`
68+
- `excludeNodeModules`
69+
- `extensions`
70+
- `compilers`
71+
- `browser`
8372
8473
See README.md for more details.
8574
*/
@@ -95,6 +84,29 @@ class ModuleConcatStream extends Readable {
9584
opts.excludeFiles[i] = path.resolve(opts.excludeFiles[i]);
9685
}
9786
}
87+
// Configure default file extensions
88+
if(!Array.isArray(opts.extensions) ) {
89+
opts.extensions = opts.extensions ? [opts.extensions] :
90+
[".js", ".json"];
91+
}
92+
// Add ".node" to the list to allow us to find native modules
93+
opts.extensions.push(".node");
94+
// Configure default compilers
95+
opts.compilers = opts.compilers || {};
96+
if(typeof opts.compilers[".json"] === "undefined") {
97+
opts.compilers[".json"] =
98+
(src, options) => "module.exports = " + src;
99+
}
100+
/* Use package.json `browser` field instead of `main` if `browser`
101+
option is set */
102+
if(opts.browser) {
103+
opts.packageFilter = (parsedPkgJson, pkgPath) => {
104+
if(parsedPkgJson.browser) {
105+
parsedPkgJson.main = parsedPkgJson.browser;
106+
}
107+
return parsedPkgJson;
108+
};
109+
}
98110
// List of files already included in the project or pending inclusion
99111
this._files = [entryModulePath];
100112
// Index pointing to the next file to included in the project
@@ -105,12 +117,9 @@ class ModuleConcatStream extends Readable {
105117
this._headerWritten = false;
106118
}
107119

108-
// Called when we should start/continue processing
120+
/* Called when we should start/continue processing.
121+
We should stop processing whenever `this.push` returns `false`. */
109122
_read(size) {
110-
this._continueProcessing();
111-
}
112-
113-
_continueProcessing() {
114123
// Write the project header
115124
if(!this._headerWritten) {
116125
this._headerWritten = true;
@@ -119,23 +128,30 @@ class ModuleConcatStream extends Readable {
119128
}
120129
// Write the next file in the project
121130
while(this._fileIndex < this._files.length) {
122-
if(!this._addFile(this._files[this._fileIndex]) )
131+
if(!this.push(this._addFile(this._files[this._fileIndex])) ) {
123132
return;
133+
}
124134
}
125135
// Write the project footer
126136
this.push(FOOTER);
127137
// Write EOF
128138
this.push(null);
129139
}
130140

131-
/* Adds the file from the given `filePath` to the project. Returns `true`
132-
if more data can be added to the stream; `false` otherwise. */
141+
/* Adds the file from the given `filePath` to the project. Returns the
142+
modified module contents (with header/footer added), which should be
143+
added to the stream. */
133144
_addFile(filePath) {
134145
try {
135146
// Read the file synchronously from disk
136147
let code = fs.readFileSync(filePath, {"encoding": "utf8"});
137148
// Mark this file as included in the project
138149
this._fileIndex++;
150+
// Compile this file if needed
151+
let compiler = this._options.compilers[path.extname(filePath)];
152+
if(compiler) {
153+
code = compiler(code, this._options);
154+
}
139155
// Remove some line comments from code
140156
code = code.replace(/(?:\r\n?|\n)\s*\/\/.*/g, "");
141157
/* Scan file for `require(...)`, `__dirname`, and `__filename`
@@ -170,24 +186,11 @@ class ModuleConcatStream extends Readable {
170186
return match;
171187
}
172188
// Get ready to resolve the module
173-
var resolveOpts = {
174-
"basedir": path.dirname(filePath),
175-
"extensions": ["", ".js", ".json", ".node"]
176-
};
177-
/* Use package.json `browser` field instead of `main` if
178-
`browser` option is set */
179-
if(this._options.browser) {
180-
resolveOpts.packageFilter = (parsedPkgJson, pkgPath) => {
181-
if(parsedPkgJson.browser) {
182-
parsedPkgJson.main = parsedPkgJson.browser;
183-
}
184-
return parsedPkgJson;
185-
};
186-
}
189+
this._options.basedir = path.dirname(filePath);
187190
// Thank you, node-resolve for making this easy!
188191
try {
189192
// Resolve the module path
190-
modulePath = resolve.sync(modulePath, resolveOpts);
193+
modulePath = resolve.sync(modulePath, this._options);
191194
// Do not replace core modules
192195
if(resolve.isCore(modulePath) ) {
193196
return match;
@@ -201,10 +204,11 @@ class ModuleConcatStream extends Readable {
201204
// Lookup this module's ID
202205
var index = this._files.indexOf(modulePath);
203206
if(index < 0) {
204-
// Not found; add this module to the project
207+
// Not added to project yet; try to add it
205208
if(!this._options.excludeFiles ||
206209
this._options.excludeFiles.indexOf(modulePath) < 0)
207210
{
211+
// Not excluded, so we're good to go!
208212
index = this._files.push(modulePath) - 1;
209213
}
210214
else {
@@ -233,28 +237,21 @@ class ModuleConcatStream extends Readable {
233237
path.relative(path.dirname(outputPath), filePath)
234238
) + ")");
235239
}
236-
/* Prepend module header and append module footer and write the
237-
included module to the stream */
238-
return this.push(
239-
// Add file header
240-
FILE_HEADER
240+
/* Return the modified module contents, prepending the module header
241+
and appending the module footer. */
242+
return FILE_HEADER
241243
.replace(/\$\{id\}/g, this._files.indexOf(filePath) )
242244
.replace(/\$\{path\}/g, filePath) +
243-
// Add extra header bit if this is a JSON file
244-
(path.extname(filePath) === ".json" ?
245-
"module.exports = " : "") +
246-
// Add modified project file
247245
code +
248-
// Add file footer
249246
FILE_FOOTER
250247
.replace(/\$\{id\}/g, this._files.indexOf(filePath) )
251-
.replace(/\$\{path\}/g, filePath)
252-
);
248+
.replace(/\$\{path\}/g, filePath);
253249
} catch(err) {
254250
process.nextTick(() => {
255251
this.emit("error", err);
256252
});
257-
return false;
253+
// Return EOF
254+
return null;
258255
}
259256
}
260257

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "node-module-concat",
3-
"version": "2.0.0-beta",
4-
"description": "CommonJS module concatenation library",
3+
"version": "2.0.0",
4+
"description": "Lightweight CommonJS module concatenation tool",
55
"main": "index.js",
66
"dependencies": {
77
"resolve": ">=1.2 <2"

test_project/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ if(require.main === module) {
2121
console.log("index is **NOT** main!");
2222
}
2323
console.log(cool);
24+
console.log("node-module-concat version:", require("../package.json").version);

0 commit comments

Comments
 (0)