support coffee-script models and client code

Load models for any filetypes registered in require.extensions.

 - Server side coffee-script requires a `require('coffee-script/register');`

 - Client side coffee-script requires Coffeeify.
This commit is contained in:
bitmage 2014-10-20 23:21:40 -07:00
parent 3961f1c615
commit e936deffe2
11 changed files with 157 additions and 15 deletions

35
docs/coffee.md Normal file
View File

@ -0,0 +1,35 @@
## Server Files in Coffee-Script
In order to create application files in coffee-script, you'll need to register the coffee-script extension:
```javascript
require('coffee-script/register');
```
You'll need to do this at any entry points for the app (e.g. the server.js file, mocha.opts, and gulp/grunt). It is recommended to leave the entry point of the app (server.js by default, specified as 'main' in package.json) as a javascript file, and then register coffee-script, and proceed with any coffee-requires.
## Client Files in Coffee-Script
You can use the Coffeeify module to include Coffee files in your browser package. Use a build script like this:
```javascript
// assuming you haven't done so already
require('coffee-script/register');
var b = browserify({
basedir: appDir,
extensions: ['.coffee'], //causes browserify to look for this extension
debug: true
});
b.transform('coffeeify'); //adds coffee compiler to the build pipeline
b.require('./app.coffee', { expose: 'browser-app' }); //requiring your file will set the entry point
boot.compileToBrowserify(appDir, b);
var bundlePath = sandbox.resolve('browser-app-bundle.js'); //remember, the final result is still '.js'
var out = fs.createWriteStream(bundlePath);
b.bundle().pipe(out);
```

View File

@ -253,13 +253,14 @@ function findModelDefinitions(rootDir, sources) {
} }
var files = tryReadDir(srcDir); var files = tryReadDir(srcDir);
files files
.filter(function(f) { .filter(function(f) {
return f[0] !== '_' && path.extname(f) === '.json'; return f[0] !== '_' && path.extname(f) === '.json';
}) })
.forEach(function(f) { .forEach(function(f) {
var fullPath = path.resolve(srcDir, f); var fullPath = path.resolve(srcDir, f);
var entry = loadModelDefinition(rootDir, fullPath); var entry = loadModelDefinition(rootDir, fullPath, files);
var modelName = entry.definition.name; var modelName = entry.definition.name;
if (!modelName) { if (!modelName) {
debug('Skipping model definition without Model name: %s', debug('Skipping model definition without Model name: %s',
@ -303,24 +304,29 @@ function resolveSourceDir(rootDir, sourceDir) {
return undefined; return undefined;
} }
function loadModelDefinition(rootDir, jsonFile) { function loadModelDefinition(rootDir, jsonFile, allFiles) {
var definition = require(jsonFile); var definition = require(jsonFile);
var basename = path.basename(jsonFile, path.extname(jsonFile));
var sourceFile = path.join( // find a matching file with `.js` or any other supported extension like `.coffee`
path.dirname(jsonFile), var base, ext, validFileType;
path.basename(jsonFile, path.extname(jsonFile))); var sourceFile = allFiles
.filter(function(f) {
ext = path.extname(f);
base = path.basename(f, ext);
validFileType = (ext !== '.node') && (ext !== '.json') &&
((typeof require.extensions[ext]) === 'function');
return validFileType && (base === basename);
})[0];
try { try {
// resolve the file to `.js` or any other supported extension like `.coffee` sourceFile = path.join(path.dirname(jsonFile), sourceFile);
sourceFile = require.resolve(sourceFile); sourceFile = require.resolve(sourceFile);
} catch (err) { } catch (err) {
debug('Model source code not found: %s - %s', sourceFile, err.code || err); debug('Model source code not found: %s - %s', sourceFile, err.code || err);
sourceFile = undefined; sourceFile = undefined;
} }
if (sourceFile === jsonFile)
sourceFile = undefined;
debug('Found model "%s" - %s %s', definition.name, debug('Found model "%s" - %s %s', definition.name,
path.relative(rootDir, jsonFile), path.relative(rootDir, jsonFile),
sourceFile ? path.relative(rootDir, sourceFile) : '(no source file)'); sourceFile ? path.relative(rootDir, sourceFile) : '(no source file)');

View File

@ -34,6 +34,9 @@
"devDependencies": { "devDependencies": {
"browserify": "^6.1.0", "browserify": "^6.1.0",
"fs-extra": "^0.12.0", "fs-extra": "^0.12.0",
"browserify": "^4.1.8",
"coffee-script": "^1.8.0",
"coffeeify": "^0.7.0",
"jshint": "^2.5.6", "jshint": "^2.5.6",
"loopback": "^2.5.0", "loopback": "^2.5.0",
"mocha": "^1.19.0", "mocha": "^1.19.0",

View File

@ -6,6 +6,31 @@ var browserify = require('browserify');
var sandbox = require('./helpers/sandbox'); var sandbox = require('./helpers/sandbox');
var vm = require('vm'); var vm = require('vm');
var compileStrategies = {
'default': function(appDir) {
var b = browserify({
basedir: appDir,
debug: true
});
b.require('./app.js', { expose: 'browser-app' });
return b;
},
'coffee': function(appDir) {
var b = browserify({
basedir: appDir,
extensions: ['.coffee'],
debug: true
});
b.transform('coffeeify');
b.require('./app.coffee', { expose: 'browser-app' });
return b;
},
};
describe('browser support', function() { describe('browser support', function() {
this.timeout(60000); // 60s to give browserify enough time to finish this.timeout(60000); // 60s to give browserify enough time to finish
@ -28,14 +53,38 @@ describe('browser support', function() {
done(); done();
}); });
}); });
it('supports coffee-script files', function(done) {
// add coffee-script to require.extensions
require('coffee-script/register');
var appDir = path.resolve(__dirname, './fixtures/coffee-app');
browserifyTestApp(appDir, 'coffee', function(err, bundlePath) {
if (err) return done(err);
var app = executeBundledApp(bundlePath);
// configured in fixtures/browser-app/boot/configure.coffee
expect(app.settings).to.have.property('custom-key', 'custom-value');
expect(Object.keys(app.models)).to.include('Customer');
expect(app.models.Customer.settings)
.to.have.property('_customized', 'Customer');
done();
});
});
}); });
function browserifyTestApp(appDir, next) { function browserifyTestApp(appDir, strategy, next) {
var b = browserify({ //set default args
basedir: appDir, if (((typeof strategy) === 'function') && !next) {
debug: true next = strategy;
}); strategy = undefined;
b.require('./app.js', { expose: 'browser-app' }); }
if (!strategy)
strategy = 'default';
var b = compileStrategies[strategy](appDir);
boot.compileToBrowserify(appDir, b); boot.compileToBrowserify(appDir, b);

View File

@ -451,6 +451,31 @@ describe('compiler', function() {
}); });
}); });
it('loads coffeescript models from `./models`', function() {
// add coffee-script to require.extensions
require('coffee-script/register');
appdir.createConfigFilesSync({}, {}, {
Car: { dataSource: 'db' }
});
appdir.writeConfigFileSync('models/car.json', { name: 'Car' });
appdir.writeFileSync('models/car.coffee', '');
var instructions = boot.compile(appdir.PATH);
expect(instructions.models).to.have.length(1);
expect(instructions.models[0]).to.eql({
name: 'Car',
config: {
dataSource: 'db'
},
definition: {
name: 'Car'
},
sourceFile: path.resolve(appdir.PATH, 'models', 'car.coffee')
});
});
it('supports `modelSources` option', function() { it('supports `modelSources` option', function() {
appdir.createConfigFilesSync({}, {}, { appdir.createConfigFilesSync({}, {}, {
Car: { dataSource: 'db' } Car: { dataSource: 'db' }

5
test/fixtures/coffee-app/app.coffee vendored Normal file
View File

@ -0,0 +1,5 @@
loopback = require 'loopback'
boot = require '../../../'
module.exports = client = loopback()
boot(client)

View File

@ -0,0 +1,2 @@
module.exports = (app) ->
app.set 'custom-key', 'custom-value'

View File

@ -0,0 +1,5 @@
{
"db": {
"connector": "remote"
}
}

View File

@ -0,0 +1,5 @@
{
"Customer": {
"dataSource": "db"
}
}

View File

@ -0,0 +1,3 @@
module.exports = (Customer) ->
Customer.settings._customized = 'Customer'
Customer.base.settings._customized = 'Base'

View File

@ -0,0 +1,4 @@
{
"name": "Customer",
"base": "User"
}