webpack.config.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. const os = require('os');
  20. const path = require('path');
  21. const webpack = require('webpack');
  22. const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
  23. .BundleAnalyzerPlugin;
  24. const { CleanWebpackPlugin } = require('clean-webpack-plugin');
  25. const CopyPlugin = require('copy-webpack-plugin');
  26. const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  27. const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
  28. const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
  29. const TerserPlugin = require('terser-webpack-plugin');
  30. const WebpackAssetsManifest = require('webpack-assets-manifest');
  31. const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
  32. // Parse command-line arguments
  33. const parsedArgs = require('minimist')(process.argv.slice(2));
  34. // input dir
  35. const APP_DIR = path.resolve(__dirname, './');
  36. // output dir
  37. const BUILD_DIR = path.resolve(__dirname, '../superset/static/assets');
  38. const {
  39. mode = 'development',
  40. devserverPort = 9000,
  41. supersetPort = 8088,
  42. measure = false,
  43. analyzeBundle = false,
  44. } = parsedArgs;
  45. const isDevMode = mode !== 'production';
  46. const plugins = [
  47. // creates a manifest.json mapping of name to hashed output used in template files
  48. new WebpackAssetsManifest({
  49. publicPath: true,
  50. // This enables us to include all relevant files for an entry
  51. entrypoints: true,
  52. // Also write to disk when using devServer
  53. // instead of only keeping manifest.json in memory
  54. // This is required to make devServer work with flask.
  55. writeToDisk: isDevMode,
  56. }),
  57. // create fresh dist/ upon build
  58. new CleanWebpackPlugin({
  59. dry: false,
  60. // required because the build directory is outside the frontend directory:
  61. dangerouslyAllowCleanPatternsOutsideProject: true,
  62. }),
  63. // expose mode variable to other modules
  64. new webpack.DefinePlugin({
  65. 'process.env.WEBPACK_MODE': JSON.stringify(mode),
  66. }),
  67. // runs type checking on a separate process to speed up the build
  68. new ForkTsCheckerWebpackPlugin({
  69. checkSyntacticErrors: true,
  70. }),
  71. new CopyPlugin([
  72. 'package.json',
  73. { from: 'images', to: 'images' },
  74. { from: 'stylesheets', to: 'stylesheets' },
  75. ]),
  76. ];
  77. if (isDevMode) {
  78. // Enable hot module replacement
  79. plugins.push(new webpack.HotModuleReplacementPlugin());
  80. } else {
  81. // text loading (webpack 4+)
  82. plugins.push(
  83. new MiniCssExtractPlugin({
  84. filename: '[name].[chunkhash].entry.css',
  85. chunkFilename: '[name].[chunkhash].chunk.css',
  86. }),
  87. );
  88. plugins.push(new OptimizeCSSAssetsPlugin());
  89. }
  90. const output = {
  91. path: BUILD_DIR,
  92. publicPath: '/static/assets/', // necessary for lazy-loaded chunks
  93. };
  94. if (isDevMode) {
  95. output.filename = '[name].[hash:8].entry.js';
  96. output.chunkFilename = '[name].[hash:8].chunk.js';
  97. } else {
  98. output.filename = '[name].[chunkhash].entry.js';
  99. output.chunkFilename = '[name].[chunkhash].chunk.js';
  100. }
  101. const PREAMBLE = ['babel-polyfill', path.join(APP_DIR, '/src/preamble.js')];
  102. function addPreamble(entry) {
  103. return PREAMBLE.concat([path.join(APP_DIR, entry)]);
  104. }
  105. const config = {
  106. node: {
  107. fs: 'empty',
  108. },
  109. entry: {
  110. theme: path.join(APP_DIR, '/src/theme.js'),
  111. preamble: PREAMBLE,
  112. addSlice: addPreamble('/src/addSlice/index.jsx'),
  113. explore: addPreamble('/src/explore/index.jsx'),
  114. dashboard: addPreamble('/src/dashboard/index.jsx'),
  115. sqllab: addPreamble('/src/SqlLab/index.jsx'),
  116. welcome: addPreamble('/src/welcome/index.jsx'),
  117. profile: addPreamble('/src/profile/index.jsx'),
  118. showSavedQuery: [path.join(APP_DIR, '/src/showSavedQuery/index.jsx')],
  119. },
  120. output,
  121. optimization: {
  122. splitChunks: {
  123. chunks: 'all',
  124. automaticNameDelimiter: '-',
  125. minChunks: 2,
  126. cacheGroups: {
  127. default: false,
  128. major: {
  129. name: 'vendors-major',
  130. test: /[\\/]node_modules\/(brace|react[-]dom|@superset[-]ui\/translation)[\\/]/,
  131. },
  132. },
  133. },
  134. },
  135. resolve: {
  136. alias: {
  137. src: path.resolve(APP_DIR, './src'),
  138. },
  139. extensions: ['.ts', '.tsx', '.js', '.jsx'],
  140. symlinks: false,
  141. },
  142. context: APP_DIR, // to automatically find tsconfig.json
  143. module: {
  144. // Uglifying mapbox-gl results in undefined errors, see
  145. // https://github.com/mapbox/mapbox-gl-js/issues/4359#issuecomment-288001933
  146. noParse: /(mapbox-gl)\.js$/,
  147. rules: [
  148. {
  149. test: /datatables\.net.*/,
  150. loader: 'imports-loader?define=>false',
  151. },
  152. {
  153. test: /\.tsx?$/,
  154. use: [
  155. { loader: 'cache-loader' },
  156. {
  157. loader: 'thread-loader',
  158. options: {
  159. // there should be 1 cpu for the fork-ts-checker-webpack-plugin
  160. workers: os.cpus().length - 1,
  161. },
  162. },
  163. {
  164. loader: 'ts-loader',
  165. options: {
  166. // transpile only in happyPack mode
  167. // type checking is done via fork-ts-checker-webpack-plugin
  168. happyPackMode: true,
  169. },
  170. },
  171. ],
  172. },
  173. {
  174. test: /\.jsx?$/,
  175. exclude: /node_modules/,
  176. include: APP_DIR,
  177. loader: 'babel-loader',
  178. },
  179. {
  180. // handle symlinked modules
  181. // for debugging @superset-ui packages via npm link
  182. test: /\.jsx?$/,
  183. include: /node_modules\/[@]superset[-]ui.+\/src/,
  184. use: [
  185. {
  186. loader: 'babel-loader',
  187. options: {
  188. presets: ['airbnb', '@babel/preset-react', '@babel/preset-env'],
  189. plugins: [
  190. 'lodash',
  191. '@babel/plugin-syntax-dynamic-import',
  192. 'react-hot-loader/babel',
  193. ],
  194. },
  195. },
  196. ],
  197. },
  198. {
  199. // handle symlinked modules
  200. // for debugging @superset-ui packages via npm link
  201. test: /\.jsx?$/,
  202. include: /node_modules\/[@]dmicros.+\/src/,
  203. use: [
  204. {
  205. loader: 'babel-loader',
  206. options: {
  207. presets: ['airbnb', '@babel/preset-react', '@babel/preset-env'],
  208. plugins: [
  209. 'lodash',
  210. '@babel/plugin-syntax-dynamic-import',
  211. 'react-hot-loader/babel',
  212. ],
  213. },
  214. },
  215. ],
  216. },
  217. {
  218. // handle symlinked modules
  219. // for debugging @superset-ui packages via npm link
  220. test: /\.jsx?$/,
  221. include: /node_modules\/[@]dmicros.+\/lib/,
  222. use: [
  223. {
  224. loader: 'babel-loader',
  225. options: {
  226. presets: ['airbnb', '@babel/preset-react', '@babel/preset-env'],
  227. plugins: [
  228. 'lodash',
  229. '@babel/plugin-syntax-dynamic-import',
  230. 'react-hot-loader/babel',
  231. ],
  232. },
  233. },
  234. ],
  235. },
  236. {
  237. test: /\.css$/,
  238. include: [APP_DIR, /superset[-]ui.+\/src/],
  239. use: [
  240. isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader,
  241. {
  242. loader: 'css-loader',
  243. options: {
  244. sourceMap: isDevMode,
  245. },
  246. },
  247. ],
  248. },
  249. {
  250. test: /\.less$/,
  251. include: APP_DIR,
  252. use: [
  253. isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader,
  254. {
  255. loader: 'css-loader',
  256. options: {
  257. sourceMap: isDevMode,
  258. },
  259. },
  260. {
  261. loader: 'less-loader',
  262. options: {
  263. sourceMap: isDevMode,
  264. },
  265. },
  266. ],
  267. },
  268. /* for css linking images */
  269. {
  270. test: /\.png$/,
  271. loader: 'url-loader',
  272. options: {
  273. limit: 10000,
  274. name: '[name].[hash:8].[ext]',
  275. },
  276. },
  277. {
  278. test: /\.(jpg|gif)$/,
  279. loader: 'file-loader',
  280. options: {
  281. name: '[name].[hash:8].[ext]',
  282. },
  283. },
  284. /* for font-awesome */
  285. {
  286. test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
  287. loader: 'url-loader?limit=10000&mimetype=application/font-woff',
  288. },
  289. {
  290. test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
  291. loader: 'file-loader',
  292. },
  293. ],
  294. },
  295. externals: {
  296. cheerio: 'window',
  297. 'react/lib/ExecutionEnvironment': true,
  298. 'react/lib/ReactContext': true,
  299. },
  300. plugins,
  301. devtool: isDevMode ? 'cheap-module-eval-source-map' : false,
  302. devServer: {
  303. historyApiFallback: true,
  304. hot: true,
  305. index: '', // This line is needed to enable root proxying
  306. inline: true,
  307. stats: { colors: true },
  308. overlay: true,
  309. port: devserverPort,
  310. // Only serves bundled files from webpack-dev-server
  311. // and proxy everything else to Superset backend
  312. proxy: {
  313. context: () => true,
  314. '/': `http://localhost:${supersetPort}`,
  315. target: `http://localhost:${supersetPort}`,
  316. },
  317. contentBase: path.join(process.cwd(), '../static/assets'),
  318. },
  319. };
  320. if (!isDevMode) {
  321. config.optimization.minimizer = [
  322. new TerserPlugin({
  323. cache: '.terser-plugin-cache/',
  324. parallel: true,
  325. extractComments: true,
  326. }),
  327. ];
  328. }
  329. // Bundle analyzer is disabled by default
  330. // Pass flag --analyzeBundle=true to enable
  331. // e.g. npm run build -- --analyzeBundle=true
  332. if (analyzeBundle) {
  333. config.plugins.push(new BundleAnalyzerPlugin());
  334. }
  335. // Speed measurement is disabled by default
  336. // Pass flag --measure=true to enable
  337. // e.g. npm run build -- --measure=true
  338. const smp = new SpeedMeasurePlugin({
  339. disable: !measure,
  340. });
  341. module.exports = smp.wrap(config);