Merge pull request #131 from PradnyaBaviskar/lb-issue-79-mixinsources

support 'mixinsources' option
This commit is contained in:
Miroslav Bajtoš 2015-05-07 16:04:14 +02:00
commit 140180f667
4 changed files with 248 additions and 43 deletions

View File

@ -85,8 +85,9 @@ module.exports = function compile(options) {
modelsRootDir, modelsConfig, modelSources, options.modelDefinitions); modelsRootDir, modelsConfig, modelSources, options.modelDefinitions);
var mixinDirs = options.mixinDirs || []; var mixinDirs = options.mixinDirs || [];
var mixinSources = options.mixinSources || modelsMeta.mixins || ['./mixins'];
var mixinInstructions = buildAllMixinInstructions( var mixinInstructions = buildAllMixinInstructions(
appRootDir, mixinDirs, options); appRootDir, mixinDirs, mixinSources, options, modelInstructions);
// When executor passes the instruction to loopback methods, // When executor passes the instruction to loopback methods,
// loopback modifies the data. Since we are loading the data using `require`, // loopback modifies the data. Since we are loading the data using `require`,
@ -646,12 +647,48 @@ function resolveAppScriptPath(rootDir, relativePath, resolveOptions) {
return (fixedFile === undefined ? resolvedPath : fixedFile); return (fixedFile === undefined ? resolvedPath : fixedFile);
} }
function buildAllMixinInstructions(appRootDir, mixinDirs, options) { function buildAllMixinInstructions(appRootDir, mixinDirs, mixinSources, options,
modelInstructions) {
var extensions = _.without(_.keys(require.extensions), var extensions = _.without(_.keys(require.extensions),
_.keys(getExcludedExtensions())); _.keys(getExcludedExtensions()));
var files = options.mixins || [];
mixinDirs.forEach(function(dir) { // load mixins from `options.mixins`
var sourceFiles = options.mixins || [];
var instructionsFromMixins = loadMixins(sourceFiles, options);
// load mixins from `options.mixinDirs`
sourceFiles = findMixinDefinitions(appRootDir, mixinDirs, extensions);
if (sourceFiles === undefined) return;
var instructionsFromMixinDirs = loadMixins(sourceFiles, options);
/* If `mixinDirs` and `mixinSources` have any directories in common,
* then remove the common directories from `mixinSources` */
mixinSources = _.difference(mixinSources, mixinDirs);
// load mixins from `options.mixinSources`
sourceFiles = findMixinDefinitions(appRootDir, mixinSources, extensions);
if (sourceFiles === undefined) return;
var instructionsFromMixinSources = loadMixins(sourceFiles, options);
// Fetch unique list of mixin names, used in models
var modelMixins = fetchMixinNamesUsedInModelInstructions(modelInstructions);
modelMixins = _.unique(modelMixins);
// Filter-in only mixins, that are used in models
instructionsFromMixinSources = filterMixinInstructionsUsingWhitelist(
instructionsFromMixinSources, modelMixins);
var mixins = _.assign(
instructionsFromMixins,
instructionsFromMixinDirs,
instructionsFromMixinSources);
return _.values(mixins);
}
function findMixinDefinitions(appRootDir, sourceDirs, extensions) {
var files = [];
sourceDirs.forEach(function(dir) {
dir = tryResolveAppPath(appRootDir, dir); dir = tryResolveAppPath(appRootDir, dir);
if (!dir) { if (!dir) {
debug('Skipping unknown module source dir %j', dir); debug('Skipping unknown module source dir %j', dir);
@ -659,8 +696,12 @@ function buildAllMixinInstructions(appRootDir, mixinDirs, options) {
} }
files = files.concat(findScripts(dir, extensions)); files = files.concat(findScripts(dir, extensions));
}); });
return files;
}
var mixins = files.map(function(filepath) { function loadMixins(sourceFiles, options) {
var mixinInstructions = {};
sourceFiles.forEach(function(filepath) {
var dir = path.dirname(filepath); var dir = path.dirname(filepath);
var ext = path.extname(filepath); var ext = path.extname(filepath);
var name = path.basename(filepath, ext); var name = path.basename(filepath, ext);
@ -674,10 +715,31 @@ function buildAllMixinInstructions(appRootDir, mixinDirs, options) {
_.extend(meta, require(metafile)); _.extend(meta, require(metafile));
} }
meta.sourceFile = filepath; meta.sourceFile = filepath;
return meta; mixinInstructions[meta.name] = meta;
}); });
return mixins; return mixinInstructions;
}
function fetchMixinNamesUsedInModelInstructions(modelInstructions) {
return _.flatten(modelInstructions
.map(function(model) {
return model.definition && model.definition.mixins ?
Object.keys(model.definition.mixins) : [];
}));
}
function filterMixinInstructionsUsingWhitelist(instructions, includeMixins) {
var instructionKeys = Object.keys(instructions);
includeMixins = _.intersection(instructionKeys, includeMixins);
var filteredInstructions = {};
instructionKeys.forEach(function(mixinName) {
if (includeMixins.indexOf(mixinName) !== -1) {
filteredInstructions[mixinName] = instructions[mixinName];
}
});
return filteredInstructions;
} }
function normalizeMixinName(str, options) { function normalizeMixinName(str, options) {

View File

@ -64,8 +64,7 @@ describe('browser support', function() {
it('loads mixins', function(done) { it('loads mixins', function(done) {
var appDir = path.resolve(__dirname, './fixtures/browser-app'); var appDir = path.resolve(__dirname, './fixtures/browser-app');
var options = { var options = {
appRootDir: appDir, appRootDir: appDir
mixinDirs: ['./mixins']
}; };
browserifyTestApp(options, function(err, bundlePath) { browserifyTestApp(options, function(err, bundlePath) {

View File

@ -1217,6 +1217,7 @@ describe('compiler', function() {
}); });
describe('for mixins', function() { describe('for mixins', function() {
describe(' - mixinDirs', function() {
function verifyMixinIsFoundViaMixinDirs(sourceFile, mixinDirs) { function verifyMixinIsFoundViaMixinDirs(sourceFile, mixinDirs) {
var appJS = appdir.writeFileSync(sourceFile, ''); var appJS = appdir.writeFileSync(sourceFile, '');
@ -1229,29 +1230,162 @@ describe('compiler', function() {
} }
it('supports `mixinDirs` option', function() { it('supports `mixinDirs` option', function() {
verifyMixinIsFoundViaMixinDirs('mixins/other.js', ['./mixins']); verifyMixinIsFoundViaMixinDirs('custom-mixins/other.js',
['./custom-mixins']);
}); });
it('resolves relative path in `mixinDirs` option', function() { it('resolves relative path in `mixinDirs` option', function() {
verifyMixinIsFoundViaMixinDirs('custom-mixins/vehicle.js', verifyMixinIsFoundViaMixinDirs('custom-mixins/other.js',
['./custom-mixins']); ['./custom-mixins']);
}); });
it('resolves module relative path in `mixinDirs` option', function() { it('resolves module relative path in `mixinDirs` option', function() {
verifyMixinIsFoundViaMixinDirs('node_modules/custom-mixins/vehicle.js', verifyMixinIsFoundViaMixinDirs('node_modules/custom-mixins/other.js',
['custom-mixins']); ['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('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() { describe('name normalization', function() {
var options; var options;
beforeEach(function() { beforeEach(function() {
options = { appRootDir: appdir.PATH, mixinDirs: ['./mixins'] }; options = { appRootDir: appdir.PATH, mixinDirs: ['./custom-mixins'] };
appdir.writeFileSync('mixins/foo.js', ''); appdir.writeFileSync('custom-mixins/foo.js', '');
appdir.writeFileSync('mixins/time-stamps.js', ''); appdir.writeFileSync('custom-mixins/time-stamps.js', '');
appdir.writeFileSync('mixins/camelCase.js', ''); appdir.writeFileSync('custom-mixins/camelCase.js', '');
appdir.writeFileSync('mixins/PascalCase.js', ''); appdir.writeFileSync('custom-mixins/PascalCase.js', '');
appdir.writeFileSync('mixins/space name.js', ''); appdir.writeFileSync('custom-mixins/space name.js', '');
}); });
it('supports classify', function() { it('supports classify', function() {
@ -1347,15 +1481,15 @@ describe('compiler', function() {
}); });
it('extends definition from JSON with same file name', 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' }); 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' }); description: 'JSON file name same as normalized name of mixin' });
var options = { appRootDir: appdir.PATH, var options = { appRootDir: appdir.PATH,
mixinDirs: ['./mixins'], mixinDirs: ['./custom-mixins'],
normalization: 'classify' }; normalization: 'classify' };
var instructions = boot.compile(options); var instructions = boot.compile(options);
@ -1367,7 +1501,6 @@ describe('compiler', function() {
} }
]); ]);
}); });
}); });
}); });

View File

@ -293,28 +293,39 @@ describe('executor', function() {
}); });
describe ('for mixins', function() { describe ('for mixins', function() {
it('defines mixins from instructions', function() { var options;
appdir.writeFileSync('mixins/example.js', beforeEach(function() {
appdir.writeFileSync('custom-mixins/example.js',
'module.exports = ' + 'module.exports = ' +
'function(Model, options) {}'); 'function(Model, options) {}');
appdir.writeFileSync('mixins/time-stamps.js', appdir.writeFileSync('custom-mixins/time-stamps.js',
'module.exports = ' + 'module.exports = ' +
'function(Model, options) {}'); 'function(Model, options) {}');
appdir.writeConfigFileSync('mixins/time-stamps.json', { appdir.writeConfigFileSync('custom-mixins/time-stamps.json', {
name: 'Timestamping' name: 'Timestamping'
}); });
var options = { options = {
appRootDir: appdir.PATH, appRootDir: appdir.PATH
mixinDirs: ['./mixins']
}; };
});
it('defines mixins from instructions - using `mixinDirs`', function() {
options.mixinDirs = ['./custom-mixins'];
boot(app, options); boot(app, options);
var modelBuilder = app.registry.modelBuilder; 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; var registry = modelBuilder.mixins.mixins;
expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']); expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']);
}); });