Merge pull request #131 from PradnyaBaviskar/lb-issue-79-mixinsources
support 'mixinsources' option
This commit is contained in:
commit
140180f667
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -1217,41 +1217,175 @@ describe('compiler', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('for mixins', function() {
|
describe('for mixins', function() {
|
||||||
function verifyMixinIsFoundViaMixinDirs(sourceFile, mixinDirs) {
|
describe(' - mixinDirs', function() {
|
||||||
var appJS = appdir.writeFileSync(sourceFile, '');
|
function verifyMixinIsFoundViaMixinDirs(sourceFile, mixinDirs) {
|
||||||
|
var appJS = appdir.writeFileSync(sourceFile, '');
|
||||||
|
|
||||||
var instructions = boot.compile({
|
var instructions = boot.compile({
|
||||||
appRootDir: appdir.PATH,
|
appRootDir: appdir.PATH,
|
||||||
mixinDirs: mixinDirs
|
mixinDirs: mixinDirs
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('supports `mixinDirs` option', function() {
|
||||||
|
verifyMixinIsFoundViaMixinDirs('custom-mixins/other.js',
|
||||||
|
['./custom-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() {
|
it('resolves module relative path in `mixinDirs` option', function() {
|
||||||
verifyMixinIsFoundViaMixinDirs('mixins/other.js', ['./mixins']);
|
verifyMixinIsFoundViaMixinDirs('node_modules/custom-mixins/other.js',
|
||||||
|
['custom-mixins']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resolves relative path in `mixinDirs` option', function() {
|
describe(' - mixinSources', function() {
|
||||||
verifyMixinIsFoundViaMixinDirs('custom-mixins/vehicle.js',
|
beforeEach(function() {
|
||||||
['./custom-mixins']);
|
appdir.createConfigFilesSync({}, {}, {
|
||||||
});
|
Car: { dataSource: 'db' }
|
||||||
|
});
|
||||||
|
appdir.writeConfigFileSync('models/car.json', {
|
||||||
|
name: 'Car',
|
||||||
|
mixins: {'TimeStamps': {} }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('resolves module relative path in `mixinDirs` option', function() {
|
function verifyMixinIsFoundViaMixinSources(sourceFile, mixinSources) {
|
||||||
verifyMixinIsFoundViaMixinDirs('node_modules/custom-mixins/vehicle.js',
|
var appJS = appdir.writeFileSync(sourceFile, '');
|
||||||
['custom-mixins']);
|
|
||||||
|
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() {
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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']);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue