@@ -60,4 +39,4 @@ const App = () => (
);
-export default hot(module)(App);
+export default hot(App);
diff --git a/superset-frontend/src/explore/index.jsx b/superset-frontend/src/explore/index.jsx
index c257009e64fd5..25a704757a523 100644
--- a/superset-frontend/src/explore/index.jsx
+++ b/superset-frontend/src/explore/index.jsx
@@ -18,6 +18,27 @@
*/
import React from 'react';
import ReactDOM from 'react-dom';
+import { createStore, applyMiddleware, compose } from 'redux';
+import thunk from 'redux-thunk';
+import logger from '../middleware/loggerMiddleware';
+import { initFeatureFlags } from '../featureFlags';
+import { initEnhancer } from '../reduxUtils';
+import getInitialState from './reducers/getInitialState';
+import rootReducer from './reducers/index';
+
import App from './App';
-ReactDOM.render(
, document.getElementById('app'));
+const exploreViewContainer = document.getElementById('app');
+const bootstrapData = JSON.parse(
+ exploreViewContainer.getAttribute('data-bootstrap'),
+);
+initFeatureFlags(bootstrapData.common.feature_flags);
+const initState = getInitialState(bootstrapData);
+
+const store = createStore(
+ rootReducer,
+ initState,
+ compose(applyMiddleware(thunk, logger), initEnhancer(false)),
+);
+
+ReactDOM.render(
, document.getElementById('app'));
diff --git a/superset-frontend/src/profile/App.jsx b/superset-frontend/src/profile/App.jsx
index b0c232e1d94b7..e021cfc2f64a5 100644
--- a/superset-frontend/src/profile/App.jsx
+++ b/superset-frontend/src/profile/App.jsx
@@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
-import { hot } from 'react-hot-loader';
+import { hot } from 'react-hot-loader/root';
import thunk from 'redux-thunk';
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import { Provider } from 'react-redux';
@@ -50,4 +50,4 @@ const Application = () => (
);
-export default hot(module)(Application);
+export default hot(Application);
diff --git a/superset-frontend/src/setup/setupApp.ts b/superset-frontend/src/setup/setupApp.ts
index ed37e1f426243..962799f440550 100644
--- a/superset-frontend/src/setup/setupApp.ts
+++ b/superset-frontend/src/setup/setupApp.ts
@@ -49,7 +49,9 @@ function toggleCheckbox(apiUrlPrefix: string, selector: string) {
export default function setupApp() {
$(document).ready(function() {
- $(':checkbox[data-checkbox-api-prefix]').change(function() {
+ $(':checkbox[data-checkbox-api-prefix]').change(function(
+ this: HTMLElement,
+ ) {
const $this = $(this);
const prefix = $this.data('checkbox-api-prefix');
const id = $this.attr('id');
diff --git a/superset-frontend/src/welcome/App.jsx b/superset-frontend/src/welcome/App.jsx
index 4f7c92c71b07a..a83f6df63a2a6 100644
--- a/superset-frontend/src/welcome/App.jsx
+++ b/superset-frontend/src/welcome/App.jsx
@@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
-import { hot } from 'react-hot-loader';
+import { hot } from 'react-hot-loader/root';
import thunk from 'redux-thunk';
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import { Provider } from 'react-redux';
@@ -72,4 +72,4 @@ const App = () => (
);
-export default hot(module)(App);
+export default hot(App);
diff --git a/superset-frontend/tsconfig.json b/superset-frontend/tsconfig.json
index 0ab027cecd2eb..1ca3ffaecc5ef 100644
--- a/superset-frontend/tsconfig.json
+++ b/superset-frontend/tsconfig.json
@@ -3,10 +3,10 @@
"allowJs": true,
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
- "esModuleInterop": true,
+ "esModuleInterop": false,
"forceConsistentCasingInFileNames": true,
- "importHelpers": true,
- "jsx": "react",
+ "importHelpers": false,
+ "jsx": "preserve",
"lib": ["dom", "esnext"],
"module": "esnext",
"moduleResolution": "node",
@@ -20,7 +20,13 @@
"sourceMap": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
- "target": "es5"
+ "target": "esnext"
},
- "include": ["./src/**/*", "./spec/**/*"]
+ "include": [
+ "./src/**/*",
+ "./spec/**/*",
+ "./node_modules/*superset-ui*/**/src/**/*",
+ "./node_modules/*superset-ui*/**/types/**/*",
+ "./node_modules/*superset-ui*/**/node_modules/**/*.d.ts"
+ ]
}
diff --git a/superset-frontend/webpack.config.js b/superset-frontend/webpack.config.js
index 3092544465c44..c26ef0351b277 100644
--- a/superset-frontend/webpack.config.js
+++ b/superset-frontend/webpack.config.js
@@ -18,7 +18,6 @@
* under the License.
*/
const fs = require('fs');
-const os = require('os');
const path = require('path');
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
@@ -32,6 +31,7 @@ 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;
+const packageConfig = require('./package.json');
// input dir
const APP_DIR = path.resolve(__dirname, './');
@@ -84,6 +84,7 @@ const plugins = [
// runs type checking on a separate process to speed up the build
new ForkTsCheckerWebpackPlugin({
+ eslint: true,
checkSyntacticErrors: true,
}),
@@ -96,10 +97,7 @@ const plugins = [
{ copyUnmodified: true },
),
];
-if (isDevMode) {
- // Enable hot module replacement
- plugins.push(new webpack.HotModuleReplacementPlugin());
-} else {
+if (!isDevMode) {
// text loading (webpack 4+)
plugins.push(
new MiniCssExtractPlugin({
@@ -110,45 +108,29 @@ if (isDevMode) {
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')];
+const PREAMBLE = [path.join(APP_DIR, '/src/preamble.js')];
+if (isDevMode) {
+ // A Superset webpage normally includes two JS bundles in dev, `theme.js` and
+ // the main entrypoint. Only the main entry should have the dev server client,
+ // otherwise the websocket client will initialize twice, creating two sockets.
+ // Ref: https://github.com/gaearon/react-hot-loader/issues/141
+ PREAMBLE.unshift(
+ `webpack-dev-server/client?http://localhost:${devserverPort}`,
+ );
+}
function addPreamble(entry) {
return PREAMBLE.concat([path.join(APP_DIR, entry)]);
}
+const babelLoader = {
+ loader: 'babel-loader',
+ options: {
+ cacheDirectory: true,
+ cacheCompression: false,
+ },
+};
+
const config = {
node: {
fs: 'empty',
@@ -165,6 +147,13 @@ const config = {
showSavedQuery: [path.join(APP_DIR, '/src/showSavedQuery/index.jsx')],
},
output,
+ stats: 'minimal',
+ performance: {
+ assetFilter(assetFilename) {
+ // don't throw size limit warning on geojson and font files
+ return !/\.(map|geojson|woff2)$/.test(assetFilename);
+ },
+ },
optimization: {
splitChunks: {
chunks: 'all',
@@ -174,7 +163,7 @@ const config = {
default: false,
major: {
name: 'vendors-major',
- test: /[\\/]node_modules\/(brace|react[-]dom|@superset[-]ui\/translation)[\\/]/,
+ test: /\/node_modules\/(brace|react|react-dom|@superset-ui\/translation|webpack.*|@babel.*)\//,
},
},
},
@@ -182,6 +171,7 @@ const config = {
resolve: {
alias: {
src: path.resolve(APP_DIR, './src'),
+ 'react-dom': '@hot-loader/react-dom',
},
extensions: ['.ts', '.tsx', '.js', '.jsx'],
symlinks: false,
@@ -199,57 +189,29 @@ const config = {
{
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,
- },
- },
+ 'thread-loader',
+ babelLoader,
{
loader: 'ts-loader',
options: {
// transpile only in happyPack mode
// type checking is done via fork-ts-checker-webpack-plugin
happyPackMode: true,
+ transpileOnly: true,
},
},
],
},
{
test: /\.jsx?$/,
- exclude: /node_modules/,
- include: APP_DIR,
- loader: 'babel-loader',
- },
- {
- // 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,
- },
- ],
+ // include source code for plugins, but exclude node_modules within them
+ exclude: [/superset-ui.*\/node_modules\/.*/],
+ include: [new RegExp(`${APP_DIR}/src`), /superset-ui.*\/src/],
+ use: [babelLoader],
},
{
test: /\.css$/,
- include: [APP_DIR, /superset[-]ui.+\/src/],
+ include: [APP_DIR, /superset-ui.+\/src/],
use: [
isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader,
{
@@ -331,8 +293,7 @@ function loadProxyConfig() {
}
if (isDevMode) {
- config.devtool = 'cheap-module-eval-source-map';
-
+ config.devtool = 'eval-cheap-module-source-map';
config.devServer = {
before() {
loadProxyConfig();
@@ -341,6 +302,8 @@ if (isDevMode) {
},
historyApiFallback: true,
hot: true,
+ injectClient: false,
+ injectHot: true,
inline: true,
stats: 'minimal',
overlay: true,
@@ -355,6 +318,24 @@ if (isDevMode) {
],
contentBase: path.join(process.cwd(), '../static/assets'),
};
+
+ // find all the symlinked plugins and use their source code for imports
+ let hasSymlink = false;
+ for (const [pkg, version] of Object.entries(packageConfig.dependencies)) {
+ const srcPath = `./node_modules/${pkg}/src`;
+ if (/superset-ui/.test(pkg) && fs.existsSync(srcPath)) {
+ console.log(
+ `[Superset Plugin] Use symlink source for ${pkg} @ ${version}`,
+ );
+ // only allow exact match so imports like `@superset-ui/plugin-name/lib`
+ // and `@superset-ui/plugin-name/esm` can still work.
+ config.resolve.alias[`${pkg}$`] = `${pkg}/src`;
+ hasSymlink = true;
+ }
+ }
+ if (hasSymlink) {
+ console.log(''); // pure cosmetic new line
+ }
} else {
config.optimization.minimizer = [
new TerserPlugin({
diff --git a/superset-frontend/webpack.proxy-config.js b/superset-frontend/webpack.proxy-config.js
index b2a7cda4e9655..a797a5cee56cf 100644
--- a/superset-frontend/webpack.proxy-config.js
+++ b/superset-frontend/webpack.proxy-config.js
@@ -68,6 +68,7 @@ function toDevHTML(originalHtml) {
loadManifest();
}
if (manifest) {
+ const loaded = new Set();
// replace bundled asset files, HTML comment tags generated by Jinja macros
// in superset/templates/superset/partials/asset_bundle.html
html = html.replace(
@@ -77,6 +78,13 @@ function toDevHTML(originalHtml) {
return `\n ${(
manifest.entrypoints[bundleName][assetType] || []
)
+ .filter(chunkFilePath => {
+ if (loaded.has(chunkFilePath)) {
+ return false;
+ }
+ loaded.add(chunkFilePath);
+ return true;
+ })
.map(chunkFilePath =>
assetType === 'css'
? `
`