Skip to content

Commit

Permalink
support 'mixinsources' option
Browse files Browse the repository at this point in the history
- By default looks for mixinsources in  directory

- Loads only mixins used through models
  • Loading branch information
PradnyaBaviskar committed May 5, 2015
1 parent 0e19f0a commit 7f11a15
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 43 deletions.
61 changes: 54 additions & 7 deletions lib/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ module.exports = function compile(options) {
modelsRootDir, modelsConfig, modelSources);

var mixinDirs = options.mixinDirs || [];
var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins'];
var mixinInstructions = buildAllMixinInstructions(
appRootDir, mixinDirs, options);
appRootDir, mixinDirs, mixinSources, options, modelInstructions);

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

function buildAllMixinInstructions(appRootDir, mixinDirs, options) {
function buildAllMixinInstructions(appRootDir, mixinDirs, mixinSources, options,
modelInstructions) {
var extensions = _.without(_.keys(require.extensions),
_.keys(getExcludedExtensions()));

var files = options.mixins || [];
var mixins = loadMixins(appRootDir, mixinDirs, files, extensions, options);
if (mixins === undefined) return;

/* If `mixinDirs` and `mixinSources` have any directories in common,
* then remove the common directories from `mixinSources` */
mixinSources = _.difference(mixinSources, mixinDirs);
files = [];
var mixinsFromMixinSources = loadMixins(appRootDir, mixinSources, files,
extensions, options);
if (mixinsFromMixinSources === undefined) return;

// Fetch unique list of mixin names, used in models
var modelMixins = _.unique(
fetchMixinNamesUsedInModelInstructions(modelInstructions));

// Filter-in only mixins, that are used in models
mixinsFromMixinSources = filterMixinInstructionsUsingWhitelist(
mixinsFromMixinSources, modelMixins);

mixins = _.reject(mixins, function(instruction) {
return (_.findWhere(mixinsFromMixinSources,
{name: instruction.name}) !== undefined);
});
mixins = mixins.concat(mixinsFromMixinSources);
return mixins;
}

mixinDirs.forEach(function(dir) {
function loadMixins(appRootDir, sourceDirs, files, extensions, options) {
sourceDirs.forEach(function(dir) {
dir = tryResolveAppPath(appRootDir, dir);
if (!dir) {
debug('Skipping unknown module source dir %j', dir);
return;
return undefined;
}
files = files.concat(findScripts(dir, extensions));
});

var mixins = files.map(function(filepath) {
var mixinInstructions = {};
files.forEach(function(filepath) {
var dir = path.dirname(filepath);
var ext = path.extname(filepath);
var name = path.basename(filepath, ext);
Expand All @@ -634,10 +665,26 @@ function buildAllMixinInstructions(appRootDir, mixinDirs, options) {
_.extend(meta, require(metafile));
}
meta.sourceFile = filepath;
return meta;
mixinInstructions[meta.name] = meta;
});

return mixins;
return _.values(mixinInstructions);
}

function fetchMixinNamesUsedInModelInstructions(modelInstructions) {
return _.flatten(modelInstructions
.map(function(model) {
return model.definition && model.definition.mixins ?
Object.keys(model.definition.mixins) : [];
}));
}

function filterMixinInstructionsUsingWhitelist(mixinInstructions,
includeMixins) {
return mixinInstructions
.filter(function(m) {
return (includeMixins.indexOf(m.name) === -1 ? false : true);
});
}

function normalizeMixinName(str, options) {
Expand Down
3 changes: 1 addition & 2 deletions test/browser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ describe('browser support', function() {
it('loads mixins', function(done) {
var appDir = path.resolve(__dirname, './fixtures/browser-app');
var options = {
appRootDir: appDir,
mixinDirs: ['./mixins']
appRootDir: appDir
};

browserifyTestApp(options, function(err, bundlePath) {
Expand Down
186 changes: 159 additions & 27 deletions test/compiler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1016,41 +1016,174 @@ describe('compiler', function() {
});

describe('for mixins', function() {
function verifyMixinIsFoundViaMixinDirs(sourceFile, mixinDirs) {
var appJS = appdir.writeFileSync(sourceFile, '');
describe(' - mixinDirs', function() {
function verifyMixinIsFoundViaMixinDirs(sourceFile, mixinDirs) {
var appJS = appdir.writeFileSync(sourceFile, '');

var instructions = boot.compile({
appRootDir: appdir.PATH,
mixinDirs: mixinDirs
var instructions = boot.compile({
appRootDir: appdir.PATH,
mixinDirs: mixinDirs
});

expect(instructions.mixins[0].sourceFile).to.eql(appJS);
}

it('supports `mixinDirs` option', function() {
verifyMixinIsFoundViaMixinDirs('mixins/other.js', ['./mixins']);
});

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

it('supports `mixinDirs` option', function() {
verifyMixinIsFoundViaMixinDirs('mixins/other.js', ['./mixins']);
it('resolves module relative path in `mixinDirs` option', function() {
verifyMixinIsFoundViaMixinDirs('node_modules/custom-mixins/other.js',
['custom-mixins']);
});
});

it('resolves relative path in `mixinDirs` option', function() {
verifyMixinIsFoundViaMixinDirs('custom-mixins/vehicle.js',
['./custom-mixins']);
});
describe(' - mixinSources', function() {
beforeEach(function() {
appdir.createConfigFilesSync({}, {}, {
Car: { dataSource: 'db' }
});
appdir.writeConfigFileSync('models/car.json', {
name: 'Car',
mixins: {'TimeStamps': {} }
});
});

function verifyMixinIsFoundViaMixinSources(sourceFile, mixinSources) {
var appJS = appdir.writeFileSync(sourceFile, '');

var instructions = boot.compile({
appRootDir: appdir.PATH,
mixinSources: mixinSources
});

expect(instructions.mixins[0].sourceFile).to.eql(appJS);
}

it('supports `mixinSources` option', function() {
verifyMixinIsFoundViaMixinSources('mixins/time-stamps.js',
['./mixins']);
});

it('resolves relative path in `mixinSources` option', function() {
verifyMixinIsFoundViaMixinSources('custom-mixins/time-stamps.js',
['./custom-mixins']);
});

it('resolves module relative path in `mixinSources` option',
function() {
verifyMixinIsFoundViaMixinSources(
'node_modules/custom-mixins/time-stamps.js',
['custom-mixins']);
});

it('supports `mixins` option in `model-config.json`', function() {
appdir.createConfigFilesSync({}, {}, {
_meta: {
mixins: ['./custom-mixins']
},
Car: {
dataSource: 'db'
}
});

var appJS = appdir.writeFileSync('custom-mixins/time-stamps.js', '');
var instructions = boot.compile(appdir.PATH);
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
});

it('resolves module relative path in `mixinDirs` option', function() {
verifyMixinIsFoundViaMixinDirs('node_modules/custom-mixins/vehicle.js',
['custom-mixins']);
it('sets by default `mixinSources` to `mixins` directory', function() {
var appJS = appdir.writeFileSync('mixins/time-stamps.js', '');
var instructions = boot.compile(appdir.PATH);
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
});

it('loads only mixins used by models', function() {
var appJS = appdir.writeFileSync('mixins/time-stamps.js', '');
appdir.writeFileSync('mixins/foo.js', '');

var instructions = boot.compile(appdir.PATH);
expect(instructions.mixins).to.have.length(1);
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
});

it('loads mixins from model using mixin name in JSON file', function() {
var appJS = appdir.writeFileSync('mixins/time-stamps.js', '');
appdir.writeConfigFileSync('mixins/time-stamps.json', {
name: 'Timestamping'
});

appdir.writeConfigFileSync('models/car.json', {
name: 'Car',
mixins: {'Timestamping': {} }
});

var instructions = boot.compile(appdir.PATH);
expect(instructions.mixins).to.have.length(1);
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
});

it('loads mixin only once for dirs common to mixinDirs & mixinSources',
function() {
var appJS = appdir.writeFileSync('custom-mixins/time-stamps.js', '');

var options = {
appRootDir: appdir.PATH,
mixinDirs: ['./custom-mixins'],
mixinSources: ['./custom-mixins']
};

var instructions = boot.compile(options);
expect(instructions.mixins).to.have.length(1);
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
});

it('loads mixin from mixinSources, when it is also found in mixinDirs',
function() {
appdir.writeFileSync('mixinDir/time-stamps.js', '');
var appJS = appdir.writeFileSync('mixinSource/time-stamps.js', '');

var options = {
appRootDir: appdir.PATH,
mixinDirs: ['./mixinDir'],
mixinSources: ['./mixinSource']
};

var instructions = boot.compile(options);
expect(instructions.mixins).to.have.length(1);
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
});

it('loads mixin from the most recent mixin definition', function() {
appdir.writeFileSync('mixins1/time-stamps.js', '');
var mixins2 = appdir.writeFileSync('mixins2/time-stamps.js', '');

var options = {
appRootDir: appdir.PATH,
mixinSources: ['./mixins1', './mixins2']
};

var instructions = boot.compile(options);
expect(instructions.mixins).to.have.length(1);
expect(instructions.mixins[0].sourceFile).to.eql(mixins2);
});
});

describe('name normalization', function() {
var options;
beforeEach(function() {
options = { appRootDir: appdir.PATH, mixinDirs: ['./mixins'] };
options = { appRootDir: appdir.PATH, mixinDirs: ['./custom-mixins'] };

appdir.writeFileSync('mixins/foo.js', '');
appdir.writeFileSync('mixins/time-stamps.js', '');
appdir.writeFileSync('mixins/camelCase.js', '');
appdir.writeFileSync('mixins/PascalCase.js', '');
appdir.writeFileSync('mixins/space name.js', '');
appdir.writeFileSync('custom-mixins/foo.js', '');
appdir.writeFileSync('custom-mixins/time-stamps.js', '');
appdir.writeFileSync('custom-mixins/camelCase.js', '');
appdir.writeFileSync('custom-mixins/PascalCase.js', '');
appdir.writeFileSync('custom-mixins/space name.js', '');
});

it('supports classify', function() {
Expand Down Expand Up @@ -1146,15 +1279,15 @@ describe('compiler', function() {
});

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

appdir.writeConfigFileSync('mixins/foo-bar.json', {
appdir.writeConfigFileSync('custom-mixins/foo-bar.json', {
description: 'JSON file name same as JS file name' });
appdir.writeConfigFileSync('mixins/FooBar.json', {
appdir.writeConfigFileSync('custom-mixins/FooBar.json', {
description: 'JSON file name same as normalized name of mixin' });

var options = { appRootDir: appdir.PATH,
mixinDirs: ['./mixins'],
mixinDirs: ['./custom-mixins'],
normalization: 'classify' };
var instructions = boot.compile(options);

Expand All @@ -1166,7 +1299,6 @@ describe('compiler', function() {
}
]);
});

});
});

Expand Down
25 changes: 18 additions & 7 deletions test/executor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,28 +293,39 @@ describe('executor', function() {
});

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

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

appdir.writeConfigFileSync('mixins/time-stamps.json', {
appdir.writeConfigFileSync('custom-mixins/time-stamps.json', {
name: 'Timestamping'
});

var options = {
appRootDir: appdir.PATH,
mixinDirs: ['./mixins']
options = {
appRootDir: appdir.PATH
};
});

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

var modelBuilder = app.registry.modelBuilder;
var registry = modelBuilder.mixins.mixins;
expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']);
});

it('defines mixins from instructions - using `mixinSources`', function() {
options.mixinSources = ['./custom-mixins'];
boot(app, options);

var modelBuilder = app.registry.modelBuilder;
var registry = modelBuilder.mixins.mixins;
expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']);
});
Expand Down

0 comments on commit 7f11a15

Please sign in to comment.