Skip to content

Commit 75d644a

Browse files
committed
Move plugins into immutable
1 parent ae29c48 commit 75d644a

5 files changed

Lines changed: 224 additions & 64 deletions

File tree

lib/async.js

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,20 +118,15 @@ module.exports = {
118118

119119
plugins
120120
.forEach(function (plugin) {
121-
if (plugin.errors && plugin.errors.length) {
121+
if (plugin.get("errors").size) {
122122
return logPluginError(plugin);
123123
}
124-
bs.registerPlugin(plugin.module, plugin.options);
124+
var jsPlugin = plugin.toJS();
125+
bs.registerPlugin(jsPlugin.module, jsPlugin.options);
125126
});
126127

127128
function logPluginError (plugin) {
128-
bs.events.emit("config:error", {
129-
msg: [
130-
"Plugin: {cyan:'%s'} not found.", plugin.name,
131-
"\nPlease check for typos :)"
132-
]
133-
});
134-
utils.fail(true);
129+
utils.fail(true, new Error("Plugin: " + plugin.get("name") + " not found"), bs.cb);
135130
}
136131

137132
done();

lib/plugins.js

Lines changed: 147 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,179 @@
1-
var Immutable = require("immutable");
2-
var qs = require("qs");
1+
var Immutable = require("immutable");
2+
var Map = Immutable.Map;
3+
var List = Immutable.List;
4+
var qs = require("qs");
5+
var path = require("path");
6+
var fs = require("fs");
37

8+
var Plugin = Immutable.Record({
9+
moduleName: "",
10+
name: "",
11+
active: true,
12+
module: undefined,
13+
options: Map({}),
14+
via: "inline",
15+
dir: process.cwd(),
16+
init: undefined,
17+
errors: List([])
18+
});
19+
20+
/**
21+
* Accept a string/object
22+
* and resolve it into the plugin format above
23+
* @param item
24+
* @returns {*}
25+
*/
426
function resolvePlugin(item) {
527

28+
/**
29+
* Handle when string was given, such as plugins: ['bs-html-injector']
30+
*/
631
if (typeof item === "string") {
732
return getFromString(item);
833
}
934

10-
if (Immutable.Map.isMap(item)) {
11-
12-
if (item.has("module")) {
13-
14-
var nameOrObj = item.get("module");
15-
var options = item.get("options");
35+
if (item.has("module")) {
1636

17-
if (options) {
18-
options = options.toJS();
19-
} else {
20-
options = {}
21-
}
37+
var nameOrObj = item.get("module");
38+
var options = item.get("options");
2239

23-
if (typeof nameOrObj === "string") {
24-
var plugin = getFromString(nameOrObj);
25-
plugin.options = options;
26-
return plugin;
27-
}
28-
29-
if (Immutable.Map.isMap(nameOrObj)) {
30-
return {
31-
module: nameOrObj.toJS(),
40+
/**
41+
* The 'module' key can be a string, this allows
42+
* inline plugin references, but with options
43+
* eg:
44+
*
45+
* bs.init({
46+
* plugins: [
47+
* {
48+
* module: './myjs-file.js'
49+
* options: {
50+
* files: "*.html"
51+
* }
52+
* }
53+
* ]
54+
* });
55+
*/
56+
if (typeof nameOrObj === "string") {
57+
return getFromString(nameOrObj)
58+
.mergeDeep({
3259
options: options
33-
}
34-
}
60+
});
3561
}
36-
if (item.has("plugin")) {
37-
// top level plugin
38-
var mod = item.toJS();
39-
return {
40-
module: mod,
41-
options: {}
42-
}
62+
63+
/**
64+
* If the plugin was given completely inline (because it needs options)
65+
* eg:
66+
*
67+
* bs.init({
68+
* plugins: [
69+
* {
70+
* module: {
71+
* plugin: function() {
72+
* console.log('My plugin code')
73+
* }
74+
* },
75+
* options: {
76+
* files: "*.html"
77+
* }
78+
* }
79+
* ]
80+
* })
81+
*/
82+
if (Immutable.Map.isMap(nameOrObj)) {
83+
return new Plugin({
84+
module: nameOrObj,
85+
options: options
86+
});
4387
}
4488
}
45-
return {
46-
name: item,
47-
options: {}
89+
90+
/**
91+
* If a module was given directly. For example, ater calling require.
92+
*
93+
* eg:
94+
* var myplugin = require('./some-js');
95+
* bs.init({plugins: [myplugin]});
96+
*/
97+
if (item.has("plugin")) {
98+
return new Plugin({
99+
module: item
100+
})
48101
}
102+
103+
/**
104+
* If we reach here, the plugin option was used incorrectly
105+
*/
106+
return new Plugin().mergeDeep({errors: [new Error("Plugin was not configured correctly")]})
49107
}
50108

51109
module.exports.resolvePlugin = resolvePlugin;
52110

111+
/**
112+
* Load a plugin from disk
113+
* @param item
114+
* @returns {*}
115+
*/
53116
function requirePlugin (item) {
54-
if (!item.module) {
55-
try {
56-
item.module = require(item.name);
57-
} catch (e) {
58-
if (e.code === "MODULE_NOT_FOUND") {
59-
item.errors = [e];
117+
118+
/**
119+
* if the "module" property already exists and
120+
* is not a string, then we bail and don't bother looking
121+
* for the file
122+
*/
123+
if (item.get("module") && typeof item.get("module") !== "string") {
124+
return item;
125+
}
126+
127+
try {
128+
/**
129+
* Try a raw node require() call - this will be how
130+
* regular "npm installed" plugins wil work
131+
*/
132+
return item.set("module", require(item.get("name")));
133+
} catch (e) {
134+
/**
135+
* If require threw an MODULE_NOT_FOUND error, try again
136+
* by resolving from cwd. This is needed since cli
137+
* users will not add ./ to the front of a path (which
138+
* node requires to resolve from cwd)
139+
*/
140+
if (e.code === "MODULE_NOT_FOUND") {
141+
var maybe = path.resolve(process.cwd(), item.get("name"));
142+
if (fs.existsSync(maybe)) {
143+
return item.set("module", require(maybe));
60144
} else {
61-
throw e;
145+
/**
146+
* Finally return a plugin that contains the error
147+
* this will be picked up later and discarded
148+
*/
149+
return item.update("errors", function (errors) {
150+
return errors.concat(e);
151+
});
62152
}
63153
}
154+
throw e;
64155
}
65-
return item;
66156
}
67157
module.exports.requirePlugin = requirePlugin;
68158

69159
function getFromString(string) {
160+
161+
/**
162+
* We allow query strings for plugins, so always split on ?
163+
*/
70164
var split = string.split("?");
165+
166+
var outGoing = new Plugin({
167+
moduleName: split[0],
168+
name: split[0]
169+
});
170+
71171
if (split.length > 1) {
72-
return {
73-
moduleName: split[0],
74-
name: split[0],
75-
options: qs.parse(split[1])
76-
}
172+
return outGoing.update("options", function (opts) {
173+
return opts.mergeDeep(qs.parse(split[1]));
174+
});
77175
}
78-
return {
79-
moduleName: split[0],
80-
name: split[0],
81-
options: {}
82-
};
176+
177+
return outGoing;
83178
}
84179

test/fixtures/plugin.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module.exports = {
2-
plugin: function () {
2+
"plugin:name": "My Plugin",
3+
"plugin": function () {
34
console.log('running here');
45
}
56
};
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"use strict";
2+
3+
var path = require("path");
4+
var assert = require("chai").assert;
5+
var browserSync = require(path.resolve("./"));
6+
7+
var pkg = require(path.resolve("package.json"));
8+
var cli = require(path.resolve(pkg.bin));
9+
10+
describe("E2E CLI `plugins` arg", function () {
11+
it("allows plugins to be registered by 'require' name only", function (done) {
12+
browserSync.reset();
13+
cli({
14+
cli: {
15+
input: ["start"],
16+
flags: {
17+
logLevel: "silent",
18+
open: false,
19+
plugins: ["bs-html-injector"]
20+
}
21+
},
22+
cb: function (err, bs) {
23+
var plugin = bs.getUserPlugin("HTML Injector");
24+
assert.equal(plugin.name, "HTML Injector");
25+
assert.equal(plugin.active, true);
26+
bs.cleanup();
27+
done();
28+
}
29+
});
30+
});
31+
it("allows plugins to be registered by 'require' name + opts", function (done) {
32+
browserSync.reset();
33+
cli({
34+
cli: {
35+
input: ["start"],
36+
flags: {
37+
logLevel: "silent",
38+
open: false,
39+
plugins: ["bs-html-injector?files[]=*.html"]
40+
}
41+
},
42+
cb: function (err, bs) {
43+
var plugin = bs.getUserPlugin("HTML Injector");
44+
assert.equal(plugin.name, "HTML Injector");
45+
assert.equal(plugin.active, true);
46+
assert.deepEqual(plugin.opts, {files: ["*.html"]});
47+
bs.cleanup();
48+
done();
49+
}
50+
});
51+
});
52+
it("allows plugins to be registered by 'path'", function (done) {
53+
browserSync.reset();
54+
cli({
55+
cli: {
56+
input: ["start"],
57+
flags: {
58+
logLevel: "silent",
59+
open: false,
60+
plugins: ["./test/fixtures/plugin.js"]
61+
}
62+
},
63+
cb: function (err, bs) {
64+
bs.cleanup();
65+
done();
66+
}
67+
});
68+
});
69+
});

test/specs/plugins/user.plugins.inline.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ describe("Plugins: Retrieving user plugins when given inline", function () {
2424
after(function () {
2525
instance.cleanup();
2626
});
27-
it("Should access to only the user-specified plugins", function (done) {
27+
it("Should access to only the user-specified plugins (1)", function (done) {
2828
assert.equal(instance.getUserPlugins().length, 1);
2929
done();
3030
});
31-
it("Should access to only the user-specified plugins", function (done) {
31+
it("Should access to only the user-specified plugins (2)", function (done) {
3232
var plugin = instance.getUserPlugins()[0];
3333
assert.equal(plugin.name, PLUGIN_NAME);
3434
done();

0 commit comments

Comments
 (0)