ima-gulp-tasks
Version:
Default gulp tasks for IMA.js applications.
398 lines (354 loc) • 11.3 kB
JavaScript
const gulp = require('gulp');
const babel = require('gulp-babel');
const browserify = require('browserify');
const cache = require('gulp-cached');
const tap = require('gulp-tap');
const concat = require('gulp-concat');
const del = require('del');
const fs = require('fs');
const insert = require('gulp-insert');
const plumber = require('gulp-plumber');
const remember = require('gulp-remember');
const save = require('gulp-save');
const source = require('vinyl-source-stream');
const sourcemaps = require('gulp-sourcemaps');
const through2 = require('through2');
const PluginError = require('plugin-error');
const uglifyEs = require('gulp-uglify-es').default;
const buffer = require('vinyl-buffer');
const clientify = require('ima-clientify').clientify;
let vendorBundle = null;
let vendorEsBundle = null;
exports.__requiresConfig = true;
exports.default = gulpConfig => {
let files = gulpConfig.files;
let babelConfig = gulpConfig.babelConfig;
function Es6ToEs5App() {
function insertSystemImports() {
return tap(file => {
file.contents = Buffer.concat([
file.contents,
new Buffer(
'\n' +
'$IMA.Loader.initAllModules();\n' +
'Promise.all([$IMA.Loader.import("app/main")])\n' +
'.catch(function (error) { \n' +
'console.error(error); \n });'
)
]);
});
}
function replaceToIMALoader() {
return tap(file => {
let content = file.contents.toString();
if (content) {
content = content.replace(/System.import/g, '$IMA.Loader.import');
content = content.replace(/System.register/g, '$IMA.Loader.register');
file.contents = new Buffer(content);
}
});
}
function excludeServerSideFile() {
return tap(file => {
let content = file.contents.toString();
if (content) {
content = clientify(content, true);
file.contents = new Buffer(content);
}
});
}
function doOnlyForLegacy(action) {
if (gulpConfig.legacyCompactMode) {
return action;
} else {
return through2.obj();
}
}
function compileToEsCode() {
if (
babelConfig.esApp.presets.length ||
babelConfig.esApp.plugins.length
) {
return babel({
babelrc: false,
moduleIds: true,
presets: babelConfig.esApp.presets,
plugins: babelConfig.esApp.plugins
});
} else {
return through2.obj();
}
}
return gulp
.src(files.app.src)
.pipe(resolveNewPath(files.app.base || '/'))
.pipe(plumber())
.pipe(sourcemaps.init())
.pipe(cache('Es6ToEs5:server:app'))
.pipe(
babel({
babelrc: false,
moduleIds: true,
presets: babelConfig.serverApp.presets,
plugins: babelConfig.serverApp.plugins
})
)
.pipe(remember('Es6ToEs5:server:app'))
.pipe(plumber.stop())
.pipe(replaceToIMALoader())
.pipe(save('Es6ToEs5:es:app:source'))
.pipe(cache('Es6ToEs5:es:app'))
.pipe(compileToEsCode())
.pipe(excludeServerSideFile())
.pipe(remember('Es6ToEs5:es:app'))
.pipe(save('Es6ToEs5:es:app:client'))
.pipe(concat(files.app.name.esClient))
.pipe(insert.wrap('(function(){\n', '\n })();\n'))
.pipe(sourcemaps.write())
.pipe(gulp.dest(files.app.dest.client))
.pipe(doOnlyForLegacy(save.restore('Es6ToEs5:es:app:client')))
.pipe(doOnlyForLegacy(cache('Es6ToEs5:legacy:app')))
.pipe(
doOnlyForLegacy(
babel({
babelrc: false,
moduleIds: true,
presets: babelConfig.app ? babelConfig.app.presets : [],
plugins: babelConfig.app ? babelConfig.app.plugins : []
})
)
)
.pipe(doOnlyForLegacy(remember('Es6ToEs5:legacy:app')))
.pipe(doOnlyForLegacy(concat(files.app.name.client)))
.pipe(doOnlyForLegacy(insert.wrap('(function(){\n', '\n })();\n')))
.pipe(doOnlyForLegacy(sourcemaps.write()))
.pipe(doOnlyForLegacy(gulp.dest(files.app.dest.client)))
.pipe(save.restore('Es6ToEs5:es:app:source'))
.pipe(concat(files.app.name.server))
.pipe(insertSystemImports())
.pipe(insert.wrap('module.exports = (function(){\n', '\n })\n'))
.pipe(gulp.dest(files.app.dest.server));
}
function Es6ToEs5Server() {
return gulp
.src(files.server.src)
.pipe(plumber())
.pipe(
babel({
babelrc: false,
presets: babelConfig.server.presets,
plugins: babelConfig.server.plugins
})
)
.pipe(plumber.stop())
.pipe(gulp.dest(files.server.dest));
}
function Es6ToEs5Vendor(done) {
let vendorModules = gulpConfig.vendorDependencies;
let serverModules = vendorModules.common.concat(vendorModules.server);
let clientModules = vendorModules.common.concat(vendorModules.client);
let testModules = vendorModules.test || [];
let serverModuleLinker = getModuleLinkerContent(serverModules);
let clientModuleLinker = getModuleLinkerContent(clientModules);
let testModuleLinker = getModuleLinkerContent(testModules);
let normalizedTmpPath = files.vendor.dest.tmp
.replace(/^\.\//, '')
.replace(/\/\.\//, '/')
.split('/');
for (let i = 0; i < normalizedTmpPath.length; i++) {
let currentPath = './' + normalizedTmpPath.slice(0, i).join('/');
try {
fs.statSync(currentPath);
} catch (e) {
fs.mkdirSync(currentPath, 0o774);
}
}
fs.writeFile(
files.vendor.dest.tmp + files.vendor.name.server,
serverModuleLinker,
error => {
if (error) {
return done(error);
}
fs.writeFile(
files.vendor.dest.tmp + files.vendor.src.client,
clientModuleLinker,
error => {
if (error || !files.vendor.src.test) {
return done(error);
}
fs.writeFile(
files.vendor.dest.tmp + files.vendor.src.test,
testModuleLinker,
error => {
done(error);
}
);
}
);
}
);
function getModuleLinkerContent(modules) {
let linkingFileHeader = `let vendorLinker = require('ima/vendorLinker.js').default;\n`;
let linkingFileFooter = `module.exports = vendorLinker;\n`;
let duplicity = [];
return (
linkingFileHeader +
modules
.map(vendorModuleName => {
let alias = vendorModuleName;
if (typeof vendorModuleName === 'object') {
alias = Object.keys(vendorModuleName)[0];
if (modules.indexOf(alias) !== -1) {
duplicity.push(alias);
}
}
return vendorModuleName;
})
.filter(
vendorModuleName =>
(typeof vendorModuleName === 'string' &&
duplicity.indexOf(vendorModuleName) === -1) ||
typeof vendorModuleName === 'object'
)
.map(vendorModuleName => {
let alias = vendorModuleName;
let moduleName = vendorModuleName;
if (typeof vendorModuleName === 'object') {
alias = Object.keys(vendorModuleName)[0];
moduleName = vendorModuleName[alias];
}
return generateVendorInclusion(alias, moduleName);
})
.join('') +
linkingFileFooter
);
}
function generateVendorInclusion(alias, moduleName) {
return `vendorLinker.set('${alias}', require('${moduleName}'));\n`;
}
}
function Es6ToEs5VendorClient(done) {
return gulp.series(gulp.parallel(vendorClient, esVendorClient), subDone => {
subDone();
done();
})();
}
function applyToBrowserifyBundle(method, config, bundle) {
return config[method].reduce((bundle, item) => {
if (!item) {
return item;
}
let [name, ...rest] = item;
return bundle[method](name, ...rest);
}, bundle);
}
function vendorClient() {
if (!gulpConfig.legacyCompactMode) {
return Promise.resolve();
}
let sourceFile = files.vendor.dest.tmp + files.vendor.src.client;
if (!vendorBundle) {
vendorBundle = browserify(sourceFile, babelConfig.vendor.options);
vendorBundle = applyToBrowserifyBundle(
'transform',
babelConfig.vendor,
vendorBundle
);
vendorBundle = applyToBrowserifyBundle(
'plugin',
babelConfig.vendor,
vendorBundle
);
}
return vendorBundle
.bundle()
.pipe(source(files.vendor.name.client))
.pipe(buffer())
.pipe(
!gulpConfig.$Debug
? uglifyEs({
compress: Object.assign({}, gulpConfig.uglifyCompression, {
ecma: 5
})
})
: through2.obj()
)
.pipe(gulp.dest(files.vendor.dest.client));
}
function esVendorClient() {
let sourceFile = files.vendor.dest.tmp + files.vendor.src.client;
if (!vendorEsBundle) {
vendorEsBundle = browserify(sourceFile, babelConfig.esVendor.options);
vendorEsBundle = applyToBrowserifyBundle(
'transform',
babelConfig.esVendor,
vendorEsBundle
);
vendorEsBundle = applyToBrowserifyBundle(
'plugin',
babelConfig.esVendor,
vendorEsBundle
);
}
return vendorEsBundle
.bundle()
.on('error', function(err) {
throw new PluginError('Es6ToEs5:vendor:client', err, {
showStack: true
});
})
.pipe(source(files.vendor.name.esClient))
.pipe(buffer())
.pipe(
!gulpConfig.$Debug
? uglifyEs({ compress: gulpConfig.uglifyCompression })
: through2.obj()
)
.pipe(gulp.dest(files.vendor.dest.client));
}
function Es6ToEs5VendorClean() {
return del([files.vendor.dest.tmp + files.vendor.name.tmp]);
}
/**
* Apply method for stream.
*
* @param {function} transformation
* @return {Stream<File>} Stream processor for files.
*/
function mapSync(transformation) {
return through2.obj(function write(chunk, _, callback) {
let mappedData;
try {
mappedData = transformation(chunk);
} catch (error) {
callback(error);
}
if (mappedData !== undefined) {
this.push(mappedData);
}
callback();
});
}
/**
* "Fix" file path for the babel task to get better-looking module names.
*
* @param {string} newBase The base directory against which the file path
* should be matched.
* @return {Stream<File>} Stream processor for files.
*/
function resolveNewPath(newBase) {
return mapSync(file => {
file.cwd += newBase;
file.base = file.cwd;
return file;
});
}
return {
'Es6ToEs5:app': Es6ToEs5App,
'Es6ToEs5:server': Es6ToEs5Server,
'Es6ToEs5:vendor': Es6ToEs5Vendor,
'Es6ToEs5:vendor:client': Es6ToEs5VendorClient,
'Es6ToEs5:vendor:clean': Es6ToEs5VendorClean
};
};