Skip to content

Commit

Permalink
Added gulp and webpack build process
Browse files Browse the repository at this point in the history
  • Loading branch information
John McLaughlin committed Mar 24, 2016
1 parent 6d1b8a0 commit c98cbf8
Show file tree
Hide file tree
Showing 3 changed files with 234 additions and 11 deletions.
199 changes: 199 additions & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
'use strict'

// create an always-enabled debug namespace.
var debugName = 'webpack';
var debug = require('debug');
debug.enable(debugName);
debug = debug(debugName);

var gulp = require('gulp');
var gutil = require('gulp-util');
var path = require('path');
var fs = require('fs');
var temp = require('temp');
var chalk = require('chalk');
var webpack = require('webpack');
var ProgressBarPlugin = require('progress-bar-webpack-plugin');
var argv = require('yargs').argv;

var paths = {
projectRoot: __dirname,
appRoot: path.join(__dirname, 'server'),
buildDir: 'build',
buildRoot: path.join(__dirname, 'build')
};

gulp.task('default', function(done) {
Webpack().run(function(err, stats) {
if(err) throw new gutil.PluginError('webpack', err);
gutil.log('[webpack]', stats.toString({
colors: true,
}));
done();
});
});

function Webpack() {
debug(`Building into ${chalk.cyan.bold('./' + paths.buildDir)}`);

// if --save-instructions is omitted, we clean up the boot instructions
// temp file automatically.
if(!argv.saveInstructions)
temp = temp.track();

// use loopback-boot to compile the boot instructions and save them to a
// temprary file. we create a resolve alias below so that
// require('boot-instructions.json') will be resolved correctly.
debug('Compiling boot instructions');

var options = {
appRootDir: paths.appRoot,
config: require(path.join(paths.appRoot, 'config.json')),
dataSources: require(path.join(paths.appRoot, 'datasources.json')),
models: require(path.join(paths.appRoot, 'model-config.json')),
middleware: require(path.join(paths.appRoot, 'middleware.json')),
};
var compile = require('loopback-boot/lib/compiler');
var ins = compile(options);

// remove config and dataSources since they will be installed at
// runtime from external files.
delete ins.config;
delete ins.dataSources;

// rewrite all paths relative to the project root.
var relative = function(p) {
return './' + path.relative(paths.projectRoot, p).replace(/\\/g, '/');
};
var relativeSourceFiles = function(arr) {
arr && arr.forEach(function(item) {
if(item.sourceFile)
item.sourceFile = relative(item.sourceFile);
});
};
relativeSourceFiles(ins.models);
relativeSourceFiles(ins.components);
relativeSourceFiles(middleware);
var bootFiles = ins.files && ins.files.boot;
if(bootFiles)
bootFiles = ins.files.boot = bootFiles.map(relative);
var middleware = ins.middleware && ins.middleware.middleware;

var instructionsFile = temp.openSync({prefix: 'boot-instructions-', suffix: '.json'});
fs.writeSync(instructionsFile.fd, JSON.stringify(ins, null, argv.saveInstructions && '\t'));
fs.closeSync(instructionsFile.fd);
debug(`Saved boot instructions to ${chalk.cyan.bold(instructionsFile.path)}`);

// Construct the dependency map for loopback-boot. It resolves all of the
// dynamic module dependencies specified by the boot instructions:
// * model definition js files
// * boot scripts
// * middleware dependencies
// Note: model JSON files are included in the instructions themselves so
// are not bundled directly.
var dependencyMap = {};
var resolveSourceFiles = function(arr) {
arr && arr.forEach(function(item) {
if(item.sourceFile)
dependencyMap[item.sourceFile] = path.resolve(paths.projectRoot, item.sourceFile);
});
};
resolveSourceFiles(ins.models);
resolveSourceFiles(ins.components);
resolveSourceFiles(middleware);
bootFiles && bootFiles.forEach(function(boot) {
dependencyMap[boot] = path.resolve(paths.projectRoot, boot);
});

// create the set of node_modules which we will externalise below. we skip
// binary modules and loopback-boot which must be bundled by webpack in order
// to resolve dynamic dependencies.
var nodeModules = new Set;
try {
fs.readdirSync(path.join(paths.projectRoot, 'node_modules'))
.forEach(function(dir) {
if(dir !== '.bin' && dir !== 'loopback-boot')
nodeModules.add(dir);
});
} catch(e) {}

// we define a master externals handler that takes care of externalising
// node_modules (largely copied from webpack-node-externals) except for
// loopback-boot. We also externalise our config.json and datasources.json
// configuration files ##### as well as any font and image files and other file extensions.
function externalsHandler(context, request, callback) {
// externalise dynamic config files. all references are re-written
// to expect them in the config file directory.
var m = request.match(/(?:^|[\/\\])(config|datasources)\.json$/);
if(m) return callback(null, `../server/${m[1]}.json`);
// the following extensions are not bundled and must be deployed as necessary.
//if(/\.(orig|txt|ttf|jpg|ts|rar)$/.test(request))
// return callback(null, request);
// externalise if the path begins with a node_modules name or if it's
// an absolute path containing /node_modules/ (the latter results from
// loopback middleware dependencies).
const pathBase = request.split(/[\/\\]/)[0];
if(nodeModules.has(pathBase) || /[\/\\]node_modules[\/\\]/.test(request))
return callback(null, 'commonjs ' + request);
// otherwise internalise (bundle) the request.
callback();
};

return webpack({
context: paths.projectRoot,
entry: './server/server.js',
target: 'node',
devtool: 'source-map',
externals: [
externalsHandler
],
output: {
libraryTarget: 'commonjs',
path: paths.buildRoot,
filename: '[name].bundle.js',
chunkFilename: '[id].bundle.js'
},
node: {
__dirname: false,
__filename: false
},
resolve: {
extensions: ['', '.json', '.js'],
modulesDirectories: ['node_modules'],
alias: {
'boot-instructions.json': instructionsFile.path
}
},
plugins: [
new ProgressBarPlugin({
format: ` ${debugName} Packing: [${chalk.yellow.bold(':bar')}] ` +
`${chalk.green.bold(':percent')} (${chalk.cyan.bold(':elapseds')})`,
width: 40,
summary: false,
clear: false
}),
new webpack.ContextReplacementPlugin(/\bloopback-boot[\/\\]lib/, '', dependencyMap)
],
module: {
// suppress warnings for require(expr) since we are expecting these from
// loopback-boot.
exprContextCritical: false,
loaders: [
/*{
test: /\.js$/i,
include: [
path.join(paths.projectRoot, 'server'),
path.join(paths.projectRoot, 'common'),
path.join(paths.projectRoot, 'node_modules', 'loopback-boot'),
],
loader: 'babel'
},*/
{
test: [/\.json$/i],
loader: 'json-loader'
},
]
},
stats: {colors: true, modules: true, reasons: true, errorDetails: true}
});
}
16 changes: 13 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,20 @@
"loopback-component-explorer": "^2.1.0",
"loopback-connector-mysql": "^1.4.7",
"loopback-datasource-juggler": "^2.7.0",
"serve-favicon": "^2.0.1"
"serve-favicon": "^2.0.1",
"source-map-support": "^0.4.0"
},
"devDependencies": {
"jshint": "^2.5.6"
"chalk": "^1.1.1",
"debug": "^2.2.0",
"gulp": "^3.9.1",
"gulp-util": "^3.0.7",
"jshint": "^2.5.6",
"json-loader": "^0.5.4",
"progress-bar-webpack-plugin": "^1.6.0",
"temp": "^0.8.3",
"webpack": "^1.12.14",
"yargs": "^4.3.2"
},
"license": "MIT"
}
}
30 changes: 22 additions & 8 deletions server/server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// install source-map support so we get mapped stack traces.
require('source-map-support').install();

var loopback = require('loopback');
var boot = require('loopback-boot');

var app = module.exports = loopback();

Expand All @@ -18,10 +20,22 @@ app.start = function() {

// Bootstrap the application, configure models, datasources and middleware.
// Sub-apps like REST API are mounted via boot scripts.
boot(app, __dirname, function(err) {
if (err) throw err;

// start the server if `$ node server.js`
if (require.main === module)
app.start();
});
console.log('Executing boot instructions...');
// instructions are provided by an explicit webpack resolve
// alias (see gulpfile.js).
var ins = require('boot-instructions.json');
// install the external dynamic configuration.
ins.config = require('./config.json');
ins.dataSources = require('./datasources.json');
var execute = require('loopback-boot/lib/executor');
execute(app, ins, function (err) {
if (err) {
console.error(`Boot error: ${err}`);
throw err;
}
console.log('Starting server...');
// NOTE/TODO: the require.main === module check fails here under webpack
// so we're not doing it.
var server = app.start();
});
console.log('Done.');

0 comments on commit c98cbf8

Please sign in to comment.