superset/superset-frontend/webpack.config.js

383 lines
10 KiB
JavaScript
Raw Normal View History

/* eslint-disable no-console */
/**
* 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 fs = require('fs');
const os = require('os');
2016-07-14 22:50:47 -04:00
const path = require('path');
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
.BundleAnalyzerPlugin;
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyPlugin = require('copy-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');
const parsedArgs = require('yargs').argv;
2016-03-18 02:44:58 -04:00
// input dir
const APP_DIR = path.resolve(__dirname, './');
// output dir
const BUILD_DIR = path.resolve(__dirname, '../superset/static/assets');
2016-07-14 22:50:47 -04:00
const {
mode = 'development',
devserverPort = 9000,
measure = false,
analyzeBundle = false,
} = parsedArgs;
const isDevMode = mode !== 'production';
const output = {
path: BUILD_DIR,
publicPath: '/static/assets/', // 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 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({
dry: false,
// required because the build directory is outside the frontend directory:
dangerouslyAllowCleanPatternsOutsideProject: true,
}),
2018-09-11 13:35:52 -04:00
// 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,
}),
new CopyPlugin(
[
'package.json',
{ from: 'images', to: 'images' },
{ from: 'stylesheets', to: 'stylesheets' },
],
{ copyUnmodified: 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 BABEL_JAVASCRIPT_OPTIONS = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: 3,
loose: true,
shippedProposals: true,
modules: false,
targets: false,
},
],
'@babel/preset-react',
],
plugins: [
'lodash',
'react-hot-loader/babel',
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-syntax-dynamic-import',
],
};
const BABEL_TYPESCRIPT_OPTIONS = {
presets: BABEL_JAVASCRIPT_OPTIONS.presets.concat([
'@babel/preset-typescript',
]),
plugins: BABEL_JAVASCRIPT_OPTIONS.plugins.concat([
'babel-plugin-typescript-to-proptypes',
]),
};
const PREAMBLE = ['babel-polyfill', path.join(APP_DIR, '/src/preamble.js')];
function addPreamble(entry) {
return PREAMBLE.concat([path.join(APP_DIR, entry)]);
}
2016-07-14 22:50:47 -04:00
const config = {
node: {
fs: 'empty',
},
2016-03-18 02:44:58 -04:00
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'),
showSavedQuery: [path.join(APP_DIR, '/src/showSavedQuery/index.jsx')],
2016-03-18 02:44:58 -04:00
},
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'],
symlinks: false,
},
context: APP_DIR, // to automatically find tsconfig.json
2016-03-18 02:44:58 -04:00
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: [
2016-11-10 11:57:43 -05:00
{
test: /datatables\.net.*/,
loader: 'imports-loader?define=>false',
2016-11-10 11:57:43 -05:00
},
{
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,
},
},
],
},
2016-03-18 02:44:58 -04:00
{
2016-07-14 22:50:47 -04:00
test: /\.jsx?$/,
exclude: /node_modules/,
include: APP_DIR,
loader: 'babel-loader',
2016-03-18 02:44:58 -04:00
},
{
// handle symlinked modules
// for debugging @superset-ui packages via npm link
test: /\.jsx?$/,
include: /node_modules\/[@]superset[-]ui.+\/src/,
use: [
{
loader: 'babel-loader',
options: BABEL_JAVASCRIPT_OPTIONS,
},
],
},
{
// handle symlinked modules
// for debugging @superset-ui packages via npm link
test: /\.tsx?$/,
include: /node_modules\/[@]superset[-]ui.+\/src/,
use: [
{
loader: 'babel-loader',
options: BABEL_TYPESCRIPT_OPTIONS,
},
],
},
2016-03-18 02:44:58 -04:00
{
test: /\.css$/,
include: [APP_DIR, /superset[-]ui.+\/src/],
use: [
isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader,
2019-11-26 13:09:39 -05:00
{
loader: 'css-loader',
options: {
sourceMap: isDevMode,
2019-11-26 13:09:39 -05:00
},
},
],
},
{
test: /\.less$/,
include: APP_DIR,
use: [
isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader,
2019-11-26 13:09:39 -05:00
{
loader: 'css-loader',
options: {
sourceMap: isDevMode,
2019-11-26 13:09:39 -05:00
},
},
{
loader: 'less-loader',
options: {
sourceMap: isDevMode,
2019-11-26 13:09:39 -05:00
},
},
],
2016-03-18 02:44:58 -04:00
},
2016-07-14 22:50:47 -04:00
/* 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]',
},
},
2016-07-14 22:50:47 -04:00
/* 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',
},
],
2016-03-18 02:44:58 -04:00
},
2016-07-14 22:50:47 -04:00
externals: {
cheerio: 'window',
'react/lib/ExecutionEnvironment': true,
'react/lib/ReactContext': true,
2016-07-14 22:50:47 -04:00
},
plugins,
devtool: false,
};
let proxyConfig = {};
const requireModule = module.require;
function loadProxyConfig() {
try {
delete require.cache[require.resolve('./webpack.proxy-config')];
proxyConfig = requireModule('./webpack.proxy-config');
} catch (e) {
if (e.code !== 'ENOENT') {
console.error('\n>> Error loading proxy config:');
console.trace(e);
}
}
}
if (isDevMode) {
config.devtool = 'cheap-module-eval-source-map';
config.devServer = {
before() {
loadProxyConfig();
// hot reloading proxy config
fs.watch('./webpack.proxy-config.js', loadProxyConfig);
},
historyApiFallback: true,
hot: true,
inline: true,
2020-02-25 11:44:26 -05:00
stats: 'minimal',
overlay: true,
port: devserverPort,
// Only serves bundled files from webpack-dev-server
// and proxy everything else to Superset backend
proxy: [
// functions are called for every request
() => {
return proxyConfig;
},
],
contentBase: path.join(process.cwd(), '../static/assets'),
};
} else {
config.optimization.minimizer = [
new TerserPlugin({
cache: '.terser-plugin-cache/',
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);