Skip to content

Commit 7f11a15

Browse files
support 'mixinsources' option
- By default looks for mixinsources in directory - Loads only mixins used through models
1 parent 0e19f0a commit 7f11a15

File tree

4 files changed

+232
-43
lines changed

4 files changed

+232
-43
lines changed

lib/compiler.js

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ module.exports = function compile(options) {
8585
modelsRootDir, modelsConfig, modelSources);
8686

8787
var mixinDirs = options.mixinDirs || [];
88+
var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins'];
8889
var mixinInstructions = buildAllMixinInstructions(
89-
appRootDir, mixinDirs, options);
90+
appRootDir, mixinDirs, mixinSources, options, modelInstructions);
9091

9192
// When executor passes the instruction to loopback methods,
9293
// loopback modifies the data. Since we are loading the data using `require`,
@@ -606,21 +607,51 @@ function resolveAppScriptPath(rootDir, relativePath, resolveOptions) {
606607
return (fixedFile === undefined ? resolvedPath : fixedFile);
607608
}
608609

609-
function buildAllMixinInstructions(appRootDir, mixinDirs, options) {
610+
function buildAllMixinInstructions(appRootDir, mixinDirs, mixinSources, options,
611+
modelInstructions) {
610612
var extensions = _.without(_.keys(require.extensions),
611613
_.keys(getExcludedExtensions()));
614+
612615
var files = options.mixins || [];
616+
var mixins = loadMixins(appRootDir, mixinDirs, files, extensions, options);
617+
if (mixins === undefined) return;
618+
619+
/* If `mixinDirs` and `mixinSources` have any directories in common,
620+
* then remove the common directories from `mixinSources` */
621+
mixinSources = _.difference(mixinSources, mixinDirs);
622+
files = [];
623+
var mixinsFromMixinSources = loadMixins(appRootDir, mixinSources, files,
624+
extensions, options);
625+
if (mixinsFromMixinSources === undefined) return;
626+
627+
// Fetch unique list of mixin names, used in models
628+
var modelMixins = _.unique(
629+
fetchMixinNamesUsedInModelInstructions(modelInstructions));
630+
631+
// Filter-in only mixins, that are used in models
632+
mixinsFromMixinSources = filterMixinInstructionsUsingWhitelist(
633+
mixinsFromMixinSources, modelMixins);
634+
635+
mixins = _.reject(mixins, function(instruction) {
636+
return (_.findWhere(mixinsFromMixinSources,
637+
{name: instruction.name}) !== undefined);
638+
});
639+
mixins = mixins.concat(mixinsFromMixinSources);
640+
return mixins;
641+
}
613642

614-
mixinDirs.forEach(function(dir) {
643+
function loadMixins(appRootDir, sourceDirs, files, extensions, options) {
644+
sourceDirs.forEach(function(dir) {
615645
dir = tryResolveAppPath(appRootDir, dir);
616646
if (!dir) {
617647
debug('Skipping unknown module source dir %j', dir);
618-
return;
648+
return undefined;
619649
}
620650
files = files.concat(findScripts(dir, extensions));
621651
});
622652

623-
var mixins = files.map(function(filepath) {
653+
var mixinInstructions = {};
654+
files.forEach(function(filepath) {
624655
var dir = path.dirname(filepath);
625656
var ext = path.extname(filepath);
626657
var name = path.basename(filepath, ext);
@@ -634,10 +665,26 @@ function buildAllMixinInstructions(appRootDir, mixinDirs, options) {
634665
_.extend(meta, require(metafile));
635666
}
636667
meta.sourceFile = filepath;
637-
return meta;
668+
mixinInstructions[meta.name] = meta;
638669
});
639670

640-
return mixins;
671+
return _.values(mixinInstructions);
672+
}
673+
674+
function fetchMixinNamesUsedInModelInstructions(modelInstructions) {
675+
return _.flatten(modelInstructions
676+
.map(function(model) {
677+
return model.definition && model.definition.mixins ?
678+
Object.keys(model.definition.mixins) : [];
679+
}));
680+
}
681+
682+
function filterMixinInstructionsUsingWhitelist(mixinInstructions,
683+
includeMixins) {
684+
return mixinInstructions
685+
.filter(function(m) {
686+
return (includeMixins.indexOf(m.name) === -1 ? false : true);
687+
});
641688
}
642689

643690
function normalizeMixinName(str, options) {

test/browser.test.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ describe('browser support', function() {
6464
it('loads mixins', function(done) {
6565
var appDir = path.resolve(__dirname, './fixtures/browser-app');
6666
var options = {
67-
appRootDir: appDir,
68-
mixinDirs: ['./mixins']
67+
appRootDir: appDir
6968
};
7069

7170
browserifyTestApp(options, function(err, bundlePath) {

test/compiler.test.js

Lines changed: 159 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,41 +1016,174 @@ describe('compiler', function() {
10161016
});
10171017

10181018
describe('for mixins', function() {
1019-
function verifyMixinIsFoundViaMixinDirs(sourceFile, mixinDirs) {
1020-
var appJS = appdir.writeFileSync(sourceFile, '');
1019+
describe(' - mixinDirs', function() {
1020+
function verifyMixinIsFoundViaMixinDirs(sourceFile, mixinDirs) {
1021+
var appJS = appdir.writeFileSync(sourceFile, '');
10211022

1022-
var instructions = boot.compile({
1023-
appRootDir: appdir.PATH,
1024-
mixinDirs: mixinDirs
1023+
var instructions = boot.compile({
1024+
appRootDir: appdir.PATH,
1025+
mixinDirs: mixinDirs
1026+
});
1027+
1028+
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
1029+
}
1030+
1031+
it('supports `mixinDirs` option', function() {
1032+
verifyMixinIsFoundViaMixinDirs('mixins/other.js', ['./mixins']);
10251033
});
10261034

1027-
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
1028-
}
1035+
it('resolves relative path in `mixinDirs` option', function() {
1036+
verifyMixinIsFoundViaMixinDirs('custom-mixins/other.js',
1037+
['./custom-mixins']);
1038+
});
10291039

1030-
it('supports `mixinDirs` option', function() {
1031-
verifyMixinIsFoundViaMixinDirs('mixins/other.js', ['./mixins']);
1040+
it('resolves module relative path in `mixinDirs` option', function() {
1041+
verifyMixinIsFoundViaMixinDirs('node_modules/custom-mixins/other.js',
1042+
['custom-mixins']);
1043+
});
10321044
});
10331045

1034-
it('resolves relative path in `mixinDirs` option', function() {
1035-
verifyMixinIsFoundViaMixinDirs('custom-mixins/vehicle.js',
1036-
['./custom-mixins']);
1037-
});
1046+
describe(' - mixinSources', function() {
1047+
beforeEach(function() {
1048+
appdir.createConfigFilesSync({}, {}, {
1049+
Car: { dataSource: 'db' }
1050+
});
1051+
appdir.writeConfigFileSync('models/car.json', {
1052+
name: 'Car',
1053+
mixins: {'TimeStamps': {} }
1054+
});
1055+
});
1056+
1057+
function verifyMixinIsFoundViaMixinSources(sourceFile, mixinSources) {
1058+
var appJS = appdir.writeFileSync(sourceFile, '');
1059+
1060+
var instructions = boot.compile({
1061+
appRootDir: appdir.PATH,
1062+
mixinSources: mixinSources
1063+
});
1064+
1065+
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
1066+
}
1067+
1068+
it('supports `mixinSources` option', function() {
1069+
verifyMixinIsFoundViaMixinSources('mixins/time-stamps.js',
1070+
['./mixins']);
1071+
});
1072+
1073+
it('resolves relative path in `mixinSources` option', function() {
1074+
verifyMixinIsFoundViaMixinSources('custom-mixins/time-stamps.js',
1075+
['./custom-mixins']);
1076+
});
1077+
1078+
it('resolves module relative path in `mixinSources` option',
1079+
function() {
1080+
verifyMixinIsFoundViaMixinSources(
1081+
'node_modules/custom-mixins/time-stamps.js',
1082+
['custom-mixins']);
1083+
});
1084+
1085+
it('supports `mixins` option in `model-config.json`', function() {
1086+
appdir.createConfigFilesSync({}, {}, {
1087+
_meta: {
1088+
mixins: ['./custom-mixins']
1089+
},
1090+
Car: {
1091+
dataSource: 'db'
1092+
}
1093+
});
1094+
1095+
var appJS = appdir.writeFileSync('custom-mixins/time-stamps.js', '');
1096+
var instructions = boot.compile(appdir.PATH);
1097+
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
1098+
});
10381099

1039-
it('resolves module relative path in `mixinDirs` option', function() {
1040-
verifyMixinIsFoundViaMixinDirs('node_modules/custom-mixins/vehicle.js',
1041-
['custom-mixins']);
1100+
it('sets by default `mixinSources` to `mixins` directory', function() {
1101+
var appJS = appdir.writeFileSync('mixins/time-stamps.js', '');
1102+
var instructions = boot.compile(appdir.PATH);
1103+
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
1104+
});
1105+
1106+
it('loads only mixins used by models', function() {
1107+
var appJS = appdir.writeFileSync('mixins/time-stamps.js', '');
1108+
appdir.writeFileSync('mixins/foo.js', '');
1109+
1110+
var instructions = boot.compile(appdir.PATH);
1111+
expect(instructions.mixins).to.have.length(1);
1112+
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
1113+
});
1114+
1115+
it('loads mixins from model using mixin name in JSON file', function() {
1116+
var appJS = appdir.writeFileSync('mixins/time-stamps.js', '');
1117+
appdir.writeConfigFileSync('mixins/time-stamps.json', {
1118+
name: 'Timestamping'
1119+
});
1120+
1121+
appdir.writeConfigFileSync('models/car.json', {
1122+
name: 'Car',
1123+
mixins: {'Timestamping': {} }
1124+
});
1125+
1126+
var instructions = boot.compile(appdir.PATH);
1127+
expect(instructions.mixins).to.have.length(1);
1128+
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
1129+
});
1130+
1131+
it('loads mixin only once for dirs common to mixinDirs & mixinSources',
1132+
function() {
1133+
var appJS = appdir.writeFileSync('custom-mixins/time-stamps.js', '');
1134+
1135+
var options = {
1136+
appRootDir: appdir.PATH,
1137+
mixinDirs: ['./custom-mixins'],
1138+
mixinSources: ['./custom-mixins']
1139+
};
1140+
1141+
var instructions = boot.compile(options);
1142+
expect(instructions.mixins).to.have.length(1);
1143+
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
1144+
});
1145+
1146+
it('loads mixin from mixinSources, when it is also found in mixinDirs',
1147+
function() {
1148+
appdir.writeFileSync('mixinDir/time-stamps.js', '');
1149+
var appJS = appdir.writeFileSync('mixinSource/time-stamps.js', '');
1150+
1151+
var options = {
1152+
appRootDir: appdir.PATH,
1153+
mixinDirs: ['./mixinDir'],
1154+
mixinSources: ['./mixinSource']
1155+
};
1156+
1157+
var instructions = boot.compile(options);
1158+
expect(instructions.mixins).to.have.length(1);
1159+
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
1160+
});
1161+
1162+
it('loads mixin from the most recent mixin definition', function() {
1163+
appdir.writeFileSync('mixins1/time-stamps.js', '');
1164+
var mixins2 = appdir.writeFileSync('mixins2/time-stamps.js', '');
1165+
1166+
var options = {
1167+
appRootDir: appdir.PATH,
1168+
mixinSources: ['./mixins1', './mixins2']
1169+
};
1170+
1171+
var instructions = boot.compile(options);
1172+
expect(instructions.mixins).to.have.length(1);
1173+
expect(instructions.mixins[0].sourceFile).to.eql(mixins2);
1174+
});
10421175
});
10431176

10441177
describe('name normalization', function() {
10451178
var options;
10461179
beforeEach(function() {
1047-
options = { appRootDir: appdir.PATH, mixinDirs: ['./mixins'] };
1180+
options = { appRootDir: appdir.PATH, mixinDirs: ['./custom-mixins'] };
10481181

1049-
appdir.writeFileSync('mixins/foo.js', '');
1050-
appdir.writeFileSync('mixins/time-stamps.js', '');
1051-
appdir.writeFileSync('mixins/camelCase.js', '');
1052-
appdir.writeFileSync('mixins/PascalCase.js', '');
1053-
appdir.writeFileSync('mixins/space name.js', '');
1182+
appdir.writeFileSync('custom-mixins/foo.js', '');
1183+
appdir.writeFileSync('custom-mixins/time-stamps.js', '');
1184+
appdir.writeFileSync('custom-mixins/camelCase.js', '');
1185+
appdir.writeFileSync('custom-mixins/PascalCase.js', '');
1186+
appdir.writeFileSync('custom-mixins/space name.js', '');
10541187
});
10551188

10561189
it('supports classify', function() {
@@ -1146,15 +1279,15 @@ describe('compiler', function() {
11461279
});
11471280

11481281
it('extends definition from JSON with same file name', function() {
1149-
var appJS = appdir.writeFileSync('mixins/foo-bar.js', '');
1282+
var appJS = appdir.writeFileSync('custom-mixins/foo-bar.js', '');
11501283

1151-
appdir.writeConfigFileSync('mixins/foo-bar.json', {
1284+
appdir.writeConfigFileSync('custom-mixins/foo-bar.json', {
11521285
description: 'JSON file name same as JS file name' });
1153-
appdir.writeConfigFileSync('mixins/FooBar.json', {
1286+
appdir.writeConfigFileSync('custom-mixins/FooBar.json', {
11541287
description: 'JSON file name same as normalized name of mixin' });
11551288

11561289
var options = { appRootDir: appdir.PATH,
1157-
mixinDirs: ['./mixins'],
1290+
mixinDirs: ['./custom-mixins'],
11581291
normalization: 'classify' };
11591292
var instructions = boot.compile(options);
11601293

@@ -1166,7 +1299,6 @@ describe('compiler', function() {
11661299
}
11671300
]);
11681301
});
1169-
11701302
});
11711303
});
11721304

test/executor.test.js

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -293,28 +293,39 @@ describe('executor', function() {
293293
});
294294

295295
describe ('for mixins', function() {
296-
it('defines mixins from instructions', function() {
297-
appdir.writeFileSync('mixins/example.js',
296+
var options;
297+
beforeEach(function() {
298+
appdir.writeFileSync('custom-mixins/example.js',
298299
'module.exports = ' +
299300
'function(Model, options) {}');
300301

301-
appdir.writeFileSync('mixins/time-stamps.js',
302+
appdir.writeFileSync('custom-mixins/time-stamps.js',
302303
'module.exports = ' +
303304
'function(Model, options) {}');
304305

305-
appdir.writeConfigFileSync('mixins/time-stamps.json', {
306+
appdir.writeConfigFileSync('custom-mixins/time-stamps.json', {
306307
name: 'Timestamping'
307308
});
308309

309-
var options = {
310-
appRootDir: appdir.PATH,
311-
mixinDirs: ['./mixins']
310+
options = {
311+
appRootDir: appdir.PATH
312312
};
313+
});
313314

315+
it('defines mixins from instructions - using `mixinDirs`', function() {
316+
options.mixinDirs = ['./custom-mixins'];
314317
boot(app, options);
315318

316319
var modelBuilder = app.registry.modelBuilder;
320+
var registry = modelBuilder.mixins.mixins;
321+
expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']);
322+
});
317323

324+
it('defines mixins from instructions - using `mixinSources`', function() {
325+
options.mixinSources = ['./custom-mixins'];
326+
boot(app, options);
327+
328+
var modelBuilder = app.registry.modelBuilder;
318329
var registry = modelBuilder.mixins.mixins;
319330
expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']);
320331
});

0 commit comments

Comments
 (0)