blob: 52065e3601b6d96a468c948586ba2a627fac02de [file] [log] [blame]
const os = require('os');
const path = require('path');
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const WebpackAssetsManifest = require('webpack-assets-manifest');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
// Parse command-line arguments
const parsedArgs = require('minimist')(process.argv.slice(2));
// input dir
const APP_DIR = path.resolve(__dirname, './');
// output dir
const BUILD_DIR = path.resolve(__dirname, './dist');
const {
mode = 'development',
devserverPort = 9000,
supersetPort = 8088,
measure = false,
analyzeBundle = false,
} = parsedArgs;
const isDevMode = mode !== 'production';
const plugins = [
// creates a manifest.json mapping of name to hashed output used in template files
new WebpackAssetsManifest({
publicPath: true,
// This enables us to include all relevant files for an entry
entrypoints: true,
// Also write to disk when using devServer
// instead of only keeping manifest.json in memory
// This is required to make devServer work with flask.
writeToDisk: isDevMode,
}),
// create fresh dist/ upon build
new CleanWebpackPlugin(['dist']),
// expose mode variable to other modules
new webpack.DefinePlugin({
'process.env.WEBPACK_MODE': JSON.stringify(mode),
}),
// runs type checking on a separate process to speed up the build
new ForkTsCheckerWebpackPlugin({
checkSyntacticErrors: true,
}),
];
if (isDevMode) {
// Enable hot module replacement
plugins.push(new webpack.HotModuleReplacementPlugin());
} else {
// text loading (webpack 4+)
plugins.push(new MiniCssExtractPlugin({
filename: '[name].[chunkhash].entry.css',
chunkFilename: '[name].[chunkhash].chunk.css',
}));
plugins.push(new OptimizeCSSAssetsPlugin());
}
const output = {
path: BUILD_DIR,
publicPath: '/static/assets/dist/', // necessary for lazy-loaded chunks
};
if (isDevMode) {
output.filename = '[name].[hash:8].entry.js';
output.chunkFilename = '[name].[hash:8].chunk.js';
} else {
output.filename = '[name].[chunkhash].entry.js';
output.chunkFilename = '[name].[chunkhash].chunk.js';
}
const PREAMBLE = [
'babel-polyfill',
path.join(APP_DIR, '/src/preamble.js'),
];
function addPreamble(entry) {
return PREAMBLE.concat([path.join(APP_DIR, entry)]);
}
const config = {
node: {
fs: 'empty',
},
entry: {
theme: path.join(APP_DIR, '/src/theme.js'),
preamble: PREAMBLE,
addSlice: addPreamble('/src/addSlice/index.jsx'),
explore: addPreamble('/src/explore/index.jsx'),
dashboard: addPreamble('/src/dashboard/index.jsx'),
sqllab: addPreamble('/src/SqlLab/index.jsx'),
welcome: addPreamble('/src/welcome/index.jsx'),
profile: addPreamble('/src/profile/index.jsx'),
},
output,
optimization: {
splitChunks: {
chunks: 'all',
automaticNameDelimiter: '-',
minChunks: 2,
cacheGroups: {
default: false,
major: {
name: 'vendors-major',
test: /[\\/]node_modules\/(brace|react[-]dom|@superset[-]ui\/translation)[\\/]/,
},
},
},
},
resolve: {
alias: {
src: path.resolve(APP_DIR, './src'),
},
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
context: APP_DIR, // to automatically find tsconfig.json
module: {
// Uglifying mapbox-gl results in undefined errors, see
// https://github.com/mapbox/mapbox-gl-js/issues/4359#issuecomment-288001933
noParse: /(mapbox-gl)\.js$/,
rules: [
{
test: /datatables\.net.*/,
loader: 'imports-loader?define=>false',
},
{
test: /\.tsx?$/,
use: [
{ loader: 'cache-loader' },
{
loader: 'thread-loader',
options: {
// there should be 1 cpu for the fork-ts-checker-webpack-plugin
workers: os.cpus().length - 1,
},
},
{
loader: 'ts-loader',
options: {
// transpile only in happyPack mode
// type checking is done via fork-ts-checker-webpack-plugin
happyPackMode: true,
},
},
],
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
{
test: /\.css$/,
include: APP_DIR,
use: [
isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
],
},
{
test: /\.less$/,
include: APP_DIR,
use: [
isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
],
},
/* for css linking images */
{
test: /\.png$/,
loader: 'url-loader',
options: {
limit: 10000,
name: '[name].[hash:8].[ext]',
},
},
{
test: /\.(jpg|gif)$/,
loader: 'file-loader',
options: {
name: '[name].[hash:8].[ext]',
},
},
/* for font-awesome */
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url-loader?limit=10000&mimetype=application/font-woff',
},
{
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader',
},
],
},
externals: {
cheerio: 'window',
'react/lib/ExecutionEnvironment': true,
'react/lib/ReactContext': true,
},
plugins,
devtool: isDevMode ? 'cheap-module-eval-source-map' : false,
devServer: {
historyApiFallback: true,
hot: true,
index: '', // This line is needed to enable root proxying
inline: true,
stats: { colors: true },
overlay: true,
port: devserverPort,
// Only serves bundled files from webpack-dev-server
// and proxy everything else to Superset backend
proxy: {
context: () => true,
'/': `http://localhost:${supersetPort}`,
target: `http://localhost:${supersetPort}`,
},
contentBase: path.join(process.cwd(), '../static/assets/dist'),
},
};
if (!isDevMode) {
config.optimization.minimizer = [
new TerserPlugin({
cache: true,
parallel: true,
extractComments: true,
}),
];
}
// Bundle analyzer is disabled by default
// Pass flag --analyzeBundle=true to enable
// e.g. npm run build -- --analyzeBundle=true
if (analyzeBundle) {
config.plugins.push(new BundleAnalyzerPlugin());
}
// Speed measurement is disabled by default
// Pass flag --measure=true to enable
// e.g. npm run build -- --measure=true
const smp = new SpeedMeasurePlugin({
disable: !measure,
});
module.exports = smp.wrap(config);