blob: 758ac56f37bf943b9ebf9008cc14faf8da6ef75d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
const autoprefixer = require('autoprefixer');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const path = require('path');
const fs = require('fs');
const PropertiesReader = require('properties-reader');
const LessToJs = require('less-vars-to-js');
const pkg = require('./../../package.json');
const webpack = require('webpack');
const pj = path.join;
const ENV = process.env.NODE_ENV || 'development';
const ROOT_DIR = pj(path.resolve(__dirname), './../..');
const SRC_DIR = pj(ROOT_DIR, 'app');
const DEST_DIR = pj(ROOT_DIR, 'dist');
const NODE_MODULES_DIR = pj(ROOT_DIR, 'node_modules');
const UTILS_NODE_MODULES_DIR = pj(ROOT_DIR, '../utils/node_modules');
const BRAND_FALLBACK_DIR = pj(ROOT_DIR, '../branding');
const BRAND_DIR = (process.env.BROOKLYN_UI_BRAND_DIR && process.env.BROOKLYN_UI_BRAND_DIR!='default')
// default accepted because mvn needs to pass a value and empty isn't allowed
? path.isAbsolute(process.env.BROOKLYN_UI_BRAND_DIR)
? process.env.BROOKLYN_UI_BRAND_DIR
: pj(ROOT_DIR, process.env.BROOKLYN_UI_BRAND_DIR)
: pj(BRAND_FALLBACK_DIR, 'brooklyn');
const brandProps = PropertiesReader(path.resolve(BRAND_FALLBACK_DIR, 'brand.properties'));
brandProps.append(path.resolve(BRAND_DIR, 'brand.properties'));
const BRAND_IDENTIFIER = brandProps.get('brand-identifier');
if (!BRAND_IDENTIFIER) {
throw("The brand.properties file in "+BRAND_DIR+" must specify a brand-identifier.");
}
var brandLess = {};
const getBrandedText = ((brandProps) => (key) => {
var branded = key.split('.').reduce(function(o, x) {
return o ? o[x] : undefined;
}, brandProps);
// TODO we should remove the "brooklyn" sections in package.json as a way of branding;
// always use the props above
return branded ? branded : key.split('.').reduce(function(o, x) {
return o ? o[x] : undefined;
}, pkg.brooklyn);
});
const getStyleLoader = function() {
var loader = [];
loader.push('css' + (ENV === 'development' ? '?sourceMap' : ''));
loader.push('postcss');
const brandLessVars = LessToJs(fs.readFileSync(path.resolve(BRAND_DIR, 'brand.less'), 'utf8'));
const brandLessVarsFromFallback = LessToJs(fs.readFileSync(path.resolve(BRAND_FALLBACK_DIR, 'brand.less'), 'utf8'));
var brandLessVarsFromProps = brandProps.getByRoot('less');
Object.assign(brandLess, brandLessVarsFromFallback, brandLessVarsFromProps, brandLessVars);
brandLess['brand-identifier'] = BRAND_IDENTIFIER;
var lessConfig = { modifyVars: brandLess };
if (ENV === 'development') {
lessConfig.sourceMap = true;
}
loader.push('less?' + JSON.stringify(lessConfig));
return loader.join('!');
};
console.log('Build with:', 'ENV', ENV, '-', 'brand-identifier', BRAND_IDENTIFIER);
const config = {
context: SRC_DIR,
entry: {
index: [
'babel-polyfill',
'index.less',
'index.js'
]
},
output: {
filename: '[name].[hash].js',
path: DEST_DIR
},
module: {
loaders: [{
test: /\.js$/i,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: [
'babel-preset-env',
'babel-preset-stage-0',
].map(require.resolve)
}
}, {
test: /\.less$/,
exclude: /index.less/,
loader: getStyleLoader()
}, {
test: /index.less$/,
loader: ExtractTextPlugin.extract(getStyleLoader())
}, {
test: /\.(jpe?g|png|gif)$/i,
loaders: [
'url-loader?limit=50000&name=assets/[name].[hash].[ext]',
'image-webpack'
]
}, {
test: /\.(woff|svg|ttf|eot)([\?]?.*)$/,
loader: 'file-loader?name=assets/[name].[hash].[ext]'
}, {
test: /\.json$/,
loader: 'raw-loader'
}, {
test: /\.html$/,
loader: 'html?interpolate&-minimize&attrs[]=img:src&attrs[]=img:data-src&attrs[]=object:data'
}]
},
resolve: {
alias: {
'brooklyn-shared': path.resolve(ROOT_DIR, '../shared'),
// user-supplied brand dir for overwrites (defaults to brooklyn brand)
'brand': BRAND_DIR,
// fallback branding info
'brand-fallback': BRAND_FALLBACK_DIR,
// brand-supplied JS definitions
'brand-js': fs.existsSync(pj(BRAND_DIR, 'brand.js')) ? pj(BRAND_DIR, 'brand.js') : pj(BRAND_FALLBACK_DIR, 'brand.js'),
},
root: SRC_DIR,
extensions: ['', '.js'],
modulesDirectories: [
NODE_MODULES_DIR,
UTILS_NODE_MODULES_DIR
]
},
resolveLoader: {
root: [NODE_MODULES_DIR],
modulesDirectories: [
NODE_MODULES_DIR,
UTILS_NODE_MODULES_DIR
]
},
postcss: [
autoprefixer({
browsers: ['last 2 versions']
})
],
ejsHtml: {
// a "brooklyn" section in package.json can contain extra info like `product.name` and `appname`
app: pkg.brooklyn,
brand: brandProps.path(),
getBrandedText: getBrandedText(brandProps.path())
},
plugins: [
new ExtractTextPlugin('[name].[chunkhash].css'),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.DefinePlugin({
__BROOKLYN_VERSION__: JSON.stringify(process.env.BROOKLYN_VERSION || '1.1.0-SNAPSHOT'),
__BUILD_NAME__: JSON.stringify(process.env.BUILD_NAME),
__BUILD_VERSION__: JSON.stringify(process.env.BUILD_VERSION),
__BUILD_BRANCH__: JSON.stringify(process.env.BUILD_BRANCH),
__BUILD_COMMIT_ID__: JSON.stringify(process.env.BUILD_COMMIT_ID),
// from bootstrap, possibly overridden, included here for access from JS
__BRAND_SUCCESS__: JSON.stringify(brandLess['@brand-success'] || '#5cb85c'),
__BRAND_DANGER__: JSON.stringify(brandLess['@brand-danger'] || '#d9534f'),
'process.env.NODE_ENV': JSON.stringify(ENV),
// used by brBrandInfo.getBrandedText
__BRAND_PROPS__: JSON.stringify(brandProps.path()),
}),
new webpack.ProvidePlugin({
'Promise': 'imports?this=>global!exports?global.Promise!es6-promise'
}),
new HtmlWebpackPlugin({
template: 'ejs-html!' + pj(SRC_DIR, 'index.html'),
chunksSortMode: 'dependency'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module) {
// This prevents stylesheet resources with the .css or .scss extension
// from being moved from their original chunk to the vendor chunk
if (module.resource && (/^.*\.(css|scss|less)$/).test(module.resource)) {
return false;
}
return module.context && module.context.indexOf('node_modules') !== -1;
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
})
]
};
if (fs.existsSync(pj(BRAND_DIR, 'build-config-tweak.js'))) {
eval(""+fs.readFileSync(pj(BRAND_DIR, 'build-config-tweak.js')));
}
if (ENV === 'development') {
config.watch = true;
config.debug = true;
config.devtool = 'source-map';
config.entry.index.push('webpack-hot-middleware/client');
config.plugins.push(new webpack.HotModuleReplacementPlugin())
} else {
config.plugins.push(
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
mangle: false,
compress: {
dead_code: true,
unused: true,
warnings: false
}
}),
new webpack.BannerPlugin(getBrandedText(brandProps.path())("banner"))
)
}
module.exports = config;