silverstripe-framework/gulpfile.js
Christopher Joe ee5b4fd8d3 Tabs support in new file/image editor
Introducing <Tabs> component based on react-bootstrap
Better support for nested fields in FormBuilder
Tweaks to get FormBuilder working with frameworktest BasicFieldsPage fields
Added exception in FormBuilder when Component is defined but not found
Added check in SingleSelectField for empty value before adding one in
Added temporary workaround for CompositeFields with no name (another story to address the actual problem)
Added asset_preview_height for File image preview, matches the defined CSS max-height
Added documentation to DBFile::PreviewLink() method
2016-09-14 14:08:59 +12:00

554 lines
17 KiB
JavaScript

const packageJson = require('./package.json');
const autoprefixer = require('autoprefixer');
const babelify = require('babelify'); // eslint-disable-line no-unused-vars
const browserify = require('browserify');
const eventStream = require('event-stream');
const glob = require('glob');
const gulp = require('gulp');
const coffee = require('gulp-coffee');
const concat = require('gulp-concat');
const merge = require('merge-stream');
const order = require('gulp-order');
const babel = require('gulp-babel');
const diff = require('gulp-diff');
const gulpif = require('gulp-if');
const notify = require('gulp-notify');
const postcss = require('gulp-postcss');
const sass = require('gulp-sass');
const sourcemaps = require('gulp-sourcemaps');
const uglify = require('gulp-uglify');
const gulpUtil = require('gulp-util');
const path = require('path');
const source = require('vinyl-source-stream');
const buffer = require('vinyl-buffer');
const semver = require('semver');
const sprity = require('sprity');
const watchify = require('watchify');
const flatten = require('gulp-flatten');
const isDev = typeof process.env.npm_config_development !== 'undefined';
process.env.NODE_ENV = isDev ? 'development' : 'production';
const PATHS = {
MODULES: './node_modules',
ADMIN: './admin',
ADMIN_IMAGES: './admin/client/dist/images',
ADMIN_CSS_SRC: './admin/client/src/styles',
ADMIN_CSS_DIST: './admin/client/dist/styles',
ADMIN_THIRDPARTY: './admin/thirdparty',
ADMIN_JS_SRC: './admin/client/src',
ADMIN_JS_DIST: './admin/client/dist/js',
ADMIN_SPRITES_SRC: './admin/client/src/sprites',
ADMIN_SPRITES_DIST: './admin/client/dist/images/sprites',
FRAMEWORK: '.',
FRAMEWORK_CSS_SRC: './client/src/styles',
FRAMEWORK_CSS_DIST: './client/dist/styles',
FRAMEWORK_THIRDPARTY: './thirdparty',
INSTALL_CSS_SRC: './dev/install/client/src/styles',
INSTALL_CSS_DIST: './dev/install/client/dist/styles',
FRAMEWORK_JS_SRC: './client/src',
FRAMEWORK_JS_DIST: './client/dist/js',
};
// Map of *.scss locations to their compile target folders
const scssFolders = {
[PATHS.FRAMEWORK_CSS_SRC]: PATHS.FRAMEWORK_CSS_DIST,
[PATHS.ADMIN_CSS_SRC]: PATHS.ADMIN_CSS_DIST,
[PATHS.INSTALL_CSS_SRC]: PATHS.INSTALL_CSS_DIST,
};
const browserifyOptions = {
debug: true,
paths: [PATHS.ADMIN_JS_SRC, PATHS.FRAMEWORK_JS_SRC],
};
const babelifyOptions = {
presets: ['es2015', 'es2015-ie', 'react'],
plugins: ['transform-object-assign', 'transform-object-rest-spread'],
ignore: /(node_modules|thirdparty)/,
comments: false,
};
const uglifyOptions = {
mangle: false,
};
// Used for autoprefixing css properties (same as Bootstrap Aplha.2 defaults)
const supportedBrowsers = [
'Chrome >= 35',
'Firefox >= 31',
'Edge >= 12',
'Explorer >= 9',
'iOS >= 8',
'Safari >= 8',
'Android 2.3',
'Android >= 4',
'Opera >= 12',
];
const blueimpFileUploadConfig = {
src: `${PATHS.MODULES}/blueimp-file-upload`,
dest: `${PATHS.FRAMEWORK_THIRDPARTY}/jquery-fileupload`,
files: [
'/cors/jquery.postmessage-transport.js',
'/cors/jquery.xdr-transport.js',
'/jquery.fileupload-ui.js',
'/jquery.fileupload.js',
'/jquery.iframe-transport.js',
],
};
const blueimpLoadImageConfig = {
src: `${PATHS.MODULES}/blueimp-load-image`,
dest: `${PATHS.FRAMEWORK_THIRDPARTY}/javascript-loadimage`,
files: ['/load-image.js'],
};
const blueimpTmplConfig = {
src: `${PATHS.MODULES}/blueimp-tmpl`,
dest: `${PATHS.FRAMEWORK_THIRDPARTY}/javascript-templates`,
files: ['/tmpl.js'],
};
const jquerySizesConfig = {
src: `${PATHS.MODULES}/jquery-sizes`,
dest: `${PATHS.ADMIN_THIRDPARTY}/jsizes`,
files: ['/lib/jquery.sizes.js'],
};
const tinymceConfig = {
src: `${PATHS.MODULES}/tinymce`,
dest: `${PATHS.FRAMEWORK_THIRDPARTY}/tinymce`,
files: [
'/tinymce.min.js', // Exclude unminified file to keep repository size down
'/jquery.tinymce.min.js',
'/themes/**',
'/skins/**',
'/plugins/**',
],
};
/**
* Copies files from a source directory to a destination directory.
*
* @param object libConfig
* @param string libConfig.src - The source directory
* @param string libConfig.dest - The destination directory
* @param array libConfig.files - The list of files to copy from the source to
* the destination directory
*/
function copyFiles(libConfig) {
libConfig.files.forEach((file) => {
const dir = path.parse(file).dir;
gulp.src(libConfig.src + file)
.pipe(gulp.dest(libConfig.dest + dir));
});
}
/**
* Diffs files in a source directory against a destination directory.
*
* @param object libConfig
* @param string libConfig.src - The source directory
* @param string libConfig.dest - The destination directory
* @param array libConfig.files - The list of files to copy from the source
* to the destination directory
*/
function diffFiles(libConfig) {
libConfig.files.forEach((file) => {
const dir = path.parse(file).dir;
gulp.src(libConfig.src + file)
.pipe(diff(libConfig.dest + dir))
.pipe(diff.reporter({ fail: true, quiet: true }))
.on('error', () => {
console.error(new Error( // eslint-disable-line
`Sanity check failed. ${libConfig.dest}${file} has been modified.`
));
process.exit(1);
});
});
}
/**
* Transforms the passed JavaScript files to UMD modules.
*
* @param array files - The files to transform.
* @param string dest - The output directory.
* @return object
*/
function transformToUmd(files, dest) {
return eventStream.merge(files.map((file) => { // eslint-disable-line
return gulp.src(file)
.pipe(babel({
presets: ['es2015'],
moduleId: `ss.${path.parse(file).name}`,
plugins: ['transform-es2015-modules-umd'],
comments: false,
}))
.on('error', notify.onError({
message: 'Error: <%= error.message %>',
}))
.pipe(gulp.dest(dest));
}));
}
// Make sure the version of Node being used is valid.
if (!semver.satisfies(process.versions.node, packageJson.engines.node)) {
console.error( // eslint-disable-line
`Invalid Node.js version. You need to be using ${packageJson.engines.node}. ` +
'If you want to manage multiple Node.js versions try https://github.com/creationix/nvm'
);
process.exit(1);
}
if (isDev) {
browserifyOptions.cache = {};
browserifyOptions.packageCache = {};
browserifyOptions.plugin = [watchify];
}
gulp.task('build', ['umd', 'bundle']);
gulp.task('bundle', ['bundle-lib', 'bundle-legacy', 'bundle-framework']);
gulp.task('bundle-lib', function bundleLib() {
const bundleFileName = 'bundle-lib.js';
const es6 = browserify(Object.assign({}, browserifyOptions,
{ entries: `${PATHS.ADMIN_JS_SRC}/bundles/lib.js` }
))
.on('update', bundleLib)
.on('log', (msg) =>
gulpUtil.log('Finished', `bundled ${bundleFileName} ${msg}`)
)
.transform('babelify', babelifyOptions)
.require('deep-freeze-strict',
{ expose: 'deep-freeze-strict' }
)
.require('react',
{ expose: 'react' }
)
.require('tether',
{ expose: 'tether' }
)
.require('react-bootstrap-ss',
{ expose: 'react-bootstrap-ss' }
)
.require('react-addons-css-transition-group',
{ expose: 'react-addons-css-transition-group' }
)
.require('react-addons-test-utils',
{ expose: 'react-addons-test-utils' }
)
.require('react-dom',
{ expose: 'react-dom' }
)
.require('react-redux',
{ expose: 'react-redux' }
)
.require('redux',
{ expose: 'redux' }
)
.require('redux-thunk',
{ expose: 'redux-thunk' }
)
.require('react-router',
{ expose: 'react-router' }
)
.require('react-router-redux',
{ expose: 'react-router-redux' }
)
.require('page.js',
{ expose: 'page.js' }
)
.require(`${PATHS.MODULES}/bootstrap/dist/js/umd/collapse.js`,
{ expose: 'bootstrap-collapse' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/Form/Form`,
{ expose: 'components/Form/Form' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/Form/FormConstants`,
{ expose: 'components/Form/FormConstants' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/FormAction/FormAction`,
{ expose: 'components/FormAction/FormAction' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/FormBuilder/FormBuilder`,
{ expose: 'components/FormBuilder/FormBuilder' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/GridField/GridField`,
{ expose: 'components/GridField/GridField' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/GridField/GridFieldCell`,
{ expose: 'components/GridField/GridFieldCell' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/GridField/GridFieldHeader`,
{ expose: 'components/GridField/GridFieldHeader' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/GridField/GridFieldHeaderCell`,
{ expose: 'components/GridField/GridFieldHeaderCell' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/GridField/GridFieldRow`,
{ expose: 'components/GridField/GridFieldRow' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/GridField/GridFieldTable`,
{ expose: 'components/GridField/GridFieldTable' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/HiddenField/HiddenField`,
{ expose: 'components/HiddenField/HiddenField' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/TextField/TextField`,
{ expose: 'components/TextField/TextField' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/Toolbar/Toolbar`,
{ expose: 'components/Toolbar/Toolbar' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/Breadcrumb/Breadcrumb`,
{ expose: 'components/Breadcrumb/Breadcrumb' }
)
.require(`${PATHS.ADMIN_JS_SRC}/state/breadcrumbs/BreadcrumbsActions`,
{ expose: 'state/breadcrumbs/BreadcrumbsActions' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/PopoverField/PopoverField`,
{ expose: 'components/PopoverField/PopoverField' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/SingleSelectField/SingleSelectField`,
{ expose: 'components/SingleSelectField/SingleSelectField' }
)
.require(`${PATHS.ADMIN_JS_SRC}/components/FormBuilderModal/FormBuilderModal`,
{ expose: 'components/FormBuilderModal/FormBuilderModal' }
)
.require(`${PATHS.FRAMEWORK_JS_SRC}/i18n.js`,
{ expose: 'i18n' }
)
.require(`${PATHS.FRAMEWORK_JS_SRC}/i18nx.js`,
{ expose: 'i18nx' }
)
.require(`${PATHS.ADMIN_JS_SRC}/lib/Config`,
{ expose: 'lib/Config' }
)
.require(`${PATHS.FRAMEWORK_JS_SRC}/jQuery.js`,
{ expose: 'jQuery' }
)
.require(`${PATHS.ADMIN_JS_SRC}/lib/ReducerRegister.js`,
{ expose: 'lib/ReducerRegister' }
)
.require(`${PATHS.ADMIN_JS_SRC}/lib/ReactRouteRegister.js`,
{ expose: 'lib/ReactRouteRegister' }
)
.require(`${PATHS.ADMIN_JS_SRC}/lib/Injector.js`,
{ expose: 'lib/Injector' }
)
.require(`${PATHS.ADMIN_JS_SRC}/lib/Router.js`,
{ expose: 'lib/Router' }
)
.require(`${PATHS.ADMIN_JS_SRC}/lib/SilverStripeComponent`,
{ expose: 'lib/SilverStripeComponent' }
)
.require(`${PATHS.ADMIN_JS_SRC}/lib/Backend`,
{ expose: 'lib/Backend' }
)
.bundle()
.on('error', notify.onError({ message: `${bundleFileName}: <%= error.message %>` }))
.pipe(source(bundleFileName))
.pipe(buffer());
const chosen = gulp.src([
`${PATHS.MODULES}/chosen/coffee/lib/*.coffee`,
`${PATHS.MODULES}/chosen/coffee/chosen.jquery.coffee`,
])
.pipe(concat('chosen.js'))
.pipe(coffee());
return merge(es6, chosen)
.pipe(order([`**/${bundleFileName}`, '**/chosen.js']))
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(concat(bundleFileName, { newLine: '\r\n;\r\n' }))
.pipe(uglify(uglifyOptions))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(PATHS.ADMIN_JS_DIST));
});
gulp.task('bundle-legacy', function bundleLeftAndMain() {
const bundleFileName = 'bundle-legacy.js';
return browserify(Object.assign({}, browserifyOptions,
{ entries: `${PATHS.ADMIN_JS_SRC}/bundles/legacy.js` }
))
.on('update', bundleLeftAndMain)
.on('log', (msg) =>
gulpUtil.log('Finished', `bundled ${bundleFileName} ${msg}`)
)
.transform('babelify', babelifyOptions)
.external('config')
.external('jQuery')
.external('i18n')
.external('i18nx')
.external('lib/Router')
.external('react')
.external('react-dom')
.external('react-bootstrap-ss')
.external('components/FormBuilderModal/FormBuilderModal')
.bundle()
.on('update', bundleLeftAndMain)
.on('error', notify.onError({ message: `${bundleFileName}: <%= error.message %>` }))
.pipe(source(bundleFileName))
.pipe(buffer())
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(uglify(uglifyOptions))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(PATHS.ADMIN_JS_DIST));
});
gulp.task('bundle-framework', function bundleBoot() {
const bundleFileName = 'bundle-framework.js';
return browserify(Object.assign({}, browserifyOptions,
{ entries: `${PATHS.ADMIN_JS_SRC}/boot/index.js` }
))
.on('update', bundleBoot)
.on('log', (msg) => {
gulpUtil.log('Finished', `bundled ${bundleFileName} ${msg}`);
})
.transform('babelify', babelifyOptions)
.external('bootstrap-collapse')
.external('components/Breadcrumb/Breadcrumb')
.external('state/breadcrumbs/BreadcrumbsActions')
.external('components/FormAction/FormAction')
.external('components/FormBuilder/FormBuilder')
.external('components/GridField/GridField')
.external('components/Toolbar/Toolbar')
.external('deep-freeze-strict')
.external('i18n')
.external('i18nx')
.external('jQuery')
.external('lib/Backend')
.external('lib/ReducerRegister')
.external('lib/ReactRouteRegister')
.external('lib/SilverStripeComponent')
.external('page.js')
.external('react-addons-test-utils')
.external('react-dom')
.external('tether')
.external('react-bootstrap-ss')
.external('react-redux')
.external('react-router-redux')
.external('react-router')
.external('react')
.external('redux-thunk')
.external('redux')
.bundle()
.on('update', bundleBoot)
.on('error', notify.onError({ message: `${bundleFileName}: <%= error.message %>` }))
.pipe(source(bundleFileName))
.pipe(buffer())
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(uglify(uglifyOptions))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(PATHS.ADMIN_JS_DIST));
});
gulp.task('sanity', () => {
diffFiles(blueimpFileUploadConfig);
diffFiles(blueimpLoadImageConfig);
diffFiles(blueimpTmplConfig);
diffFiles(jquerySizesConfig);
diffFiles(tinymceConfig);
});
gulp.task('thirdparty', () => {
copyFiles(blueimpFileUploadConfig);
copyFiles(blueimpLoadImageConfig);
copyFiles(blueimpTmplConfig);
copyFiles(jquerySizesConfig);
copyFiles(tinymceConfig);
});
gulp.task('umd', ['umd-admin', 'umd-framework'], () => {
if (isDev) {
gulp.watch(`${PATHS.ADMIN_JS_SRC}/legacy/*.js`, ['umd-admin']);
gulp.watch(`${PATHS.FRAMEWORK_JS_SRC}/**/*.js`, ['umd-framework']);
}
});
gulp.task('umd-admin', () => {
const files = glob.sync(
`${PATHS.ADMIN_JS_SRC}/legacy/*.js`,
{ ignore: `${PATHS.ADMIN_JS_SRC}/LeftAndMain.!(Ping).js` }
);
return transformToUmd(files, PATHS.ADMIN_JS_DIST);
});
gulp.task('umd-framework', () => { // eslint-disable-line
return transformToUmd(glob.sync(
`${PATHS.FRAMEWORK_JS_SRC}/**/*.js`),
PATHS.FRAMEWORK_JS_DIST
);
});
/*
* Takes individual images and compiles them together into sprites
*/
gulp.task('sprites', () => { // eslint-disable-line
return sprity.src({
src: `${PATHS.ADMIN_SPRITES_SRC}/**/*.{png,jpg}`,
cssPath: '../images/sprites',
style: './_sprity.scss',
processor: 'sass',
split: true,
margin: 0,
})
.pipe(
gulpif(
'*.png',
gulp.dest(PATHS.ADMIN_SPRITES_DIST),
gulp.dest(`${PATHS.ADMIN_CSS_SRC}/legacy`)
)
);
});
gulp.task('css', ['compile:css'], () => {
if (isDev) {
Object.keys(scssFolders).forEach((sourceFolder) => {
gulp.watch(`${sourceFolder}/**/*.scss`, ['compile:css']);
});
}
});
/*
* Compiles scss into css
* Watches for changes if --development flag is given
*/
gulp.task('compile:css', () => {
const tasks = Object.keys(scssFolders).map((sourceFolder) => { // eslint-disable-line
const targetFolder = scssFolders[sourceFolder];
return gulp.src(`${sourceFolder}/**/*.scss`)
.pipe(sourcemaps.init())
.pipe(
sass({
outputStyle: 'compressed',
importer: (url, prev, done) => {
if (url.match(/^compass\//)) {
done({ file: 'client/src/styles/_compasscompat.scss' });
} else {
done();
}
},
})
.on('error', notify.onError({
message: 'Error: <%= error.message %>',
}))
)
.pipe(postcss([autoprefixer({ browsers: supportedBrowsers })]))
.pipe(sourcemaps.write())
.pipe(flatten()) // avoid legacy/ paths in CSS output
.pipe(gulp.dest(targetFolder));
});
return tasks;
});