webpack.config.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. // const path = require('path');
  2. const path = require("path");
  3. const { composePlugins, withNx } = require("@nx/webpack");
  4. const { withReact } = require("@nx/react");
  5. const { merge } = require("webpack-merge");
  6. require("dotenv").config({
  7. // resolve the .env file in the root of the project ../
  8. path: path.resolve(__dirname, "../.env"),
  9. });
  10. const MiniCssExtractPlugin = require("mini-css-extract-plugin");
  11. const { EnvironmentPlugin, DefinePlugin, ProgressPlugin, optimize } = require("webpack");
  12. const TerserPlugin = require("terser-webpack-plugin");
  13. const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
  14. const RELEASE = require("./release").getReleaseName();
  15. const css_prefix = "lsf-";
  16. const mode = process.env.BUILD_MODULE ? "production" : process.env.NODE_ENV || "development";
  17. const isDevelopment = mode !== "production";
  18. const devtool = process.env.NODE_ENV === "production" ? "source-map" : "cheap-module-source-map";
  19. const FRONTEND_HMR = process.env.FRONTEND_HMR === "true";
  20. const FRONTEND_HOSTNAME = FRONTEND_HMR ? process.env.FRONTEND_HOSTNAME || "http://localhost:8010" : "";
  21. const DJANGO_HOSTNAME = process.env.DJANGO_HOSTNAME || "http://localhost:8080";
  22. const HMR_PORT = FRONTEND_HMR ? +new URL(FRONTEND_HOSTNAME).port : 8010;
  23. const LOCAL_ENV = {
  24. NODE_ENV: mode,
  25. CSS_PREFIX: css_prefix,
  26. RELEASE_NAME: RELEASE,
  27. };
  28. const BUILD = {
  29. NO_MINIMIZE: isDevelopment || !!process.env.BUILD_NO_MINIMIZATION,
  30. };
  31. const plugins = [
  32. new MiniCssExtractPlugin(),
  33. new DefinePlugin({
  34. "process.env.CSS_PREFIX": JSON.stringify(css_prefix),
  35. }),
  36. new EnvironmentPlugin(LOCAL_ENV),
  37. ];
  38. const optimizer = () => {
  39. const result = {
  40. minimize: true,
  41. minimizer: [],
  42. };
  43. if (mode === "production") {
  44. result.minimizer.push(
  45. new TerserPlugin({
  46. parallel: true,
  47. }),
  48. new CssMinimizerPlugin({
  49. parallel: true,
  50. }),
  51. );
  52. }
  53. if (BUILD.NO_MINIMIZE) {
  54. result.minimize = false;
  55. result.minimizer = undefined;
  56. }
  57. if (process.env.MODE?.startsWith("standalone")) {
  58. result.runtimeChunk = false;
  59. result.splitChunks = { cacheGroups: { default: false } };
  60. }
  61. return result;
  62. };
  63. // Nx plugins for webpack.
  64. module.exports = composePlugins(
  65. withNx({
  66. nx: {
  67. svgr: true,
  68. },
  69. skipTypeChecking: true,
  70. }),
  71. withReact({ svgr: true }),
  72. (config) => {
  73. // LS entrypoint
  74. if (!process.env.MODE?.startsWith("standalone")) {
  75. config.entry = {
  76. main: {
  77. import: path.resolve(__dirname, "apps/labelstudio/src/main.tsx"),
  78. },
  79. };
  80. config.output = {
  81. ...config.output,
  82. uniqueName: "labelstudio",
  83. publicPath:
  84. isDevelopment && FRONTEND_HOSTNAME
  85. ? `${FRONTEND_HOSTNAME}/react-app/`
  86. : process.env.MODE === "standalone-playground"
  87. ? "/playground-assets/"
  88. : "auto",
  89. scriptType: "text/javascript",
  90. };
  91. config.optimization = {
  92. runtimeChunk: "single",
  93. sideEffects: true,
  94. splitChunks: {
  95. cacheGroups: {
  96. commonVendor: {
  97. test: /[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom|mobx|mobx-react|mobx-react-lite|mobx-state-tree)[\\/]/,
  98. name: "vendor",
  99. chunks: "all",
  100. },
  101. defaultVendors: {
  102. test: /[\\/]node_modules[\\/]/,
  103. priority: -10,
  104. reuseExistingChunk: true,
  105. chunks: "async",
  106. },
  107. default: {
  108. minChunks: 2,
  109. priority: -20,
  110. reuseExistingChunk: true,
  111. chunks: "async",
  112. },
  113. },
  114. },
  115. };
  116. }
  117. config.resolve.fallback = {
  118. fs: false,
  119. path: false,
  120. crypto: false,
  121. worker_threads: false,
  122. };
  123. config.experiments = {
  124. cacheUnaffected: true,
  125. syncWebAssembly: true,
  126. asyncWebAssembly: true,
  127. };
  128. config.module.rules.forEach((rule) => {
  129. const testString = rule.test.toString();
  130. const isScss = testString.includes("scss");
  131. const isCssModule = testString.includes(".module");
  132. if (isScss) {
  133. rule.oneOf.forEach((loader) => {
  134. if (loader.use) {
  135. const cssLoader = loader.use.find((use) => use.loader && use.loader.includes("css-loader"));
  136. if (cssLoader && cssLoader.options) {
  137. cssLoader.options.modules = {
  138. mode: "local",
  139. auto: true,
  140. namedExport: false,
  141. localIdentName: "[local]--[hash:base64:5]",
  142. };
  143. }
  144. }
  145. });
  146. }
  147. if (rule.test.toString().match(/scss|sass/) && !isCssModule) {
  148. const r = rule.oneOf.filter((r) => {
  149. // we don't need rules that don't have loaders
  150. if (!r.use) return false;
  151. const testString = r.test.toString();
  152. // we also don't need css modules as these are used directly
  153. // in the code and don't need prefixing
  154. if (testString.match(/module|raw|antd/)) return false;
  155. // we only target pre-processors that has 'css-loader included'
  156. return testString.match(/scss|sass/) && r.use.some((u) => u.loader && u.loader.includes("css-loader"));
  157. });
  158. r.forEach((_r) => {
  159. const cssLoader = _r.use.find((use) => use.loader && use.loader.includes("css-loader"));
  160. if (!cssLoader) return;
  161. const isSASS = _r.use.some((use) => use.loader && use.loader.match(/sass|scss/));
  162. if (isSASS) _r.exclude = /node_modules/;
  163. if (cssLoader.options) {
  164. cssLoader.options.modules = {
  165. localIdentName: `${css_prefix}[local]`, // Customize this format
  166. getLocalIdent(_ctx, _ident, className) {
  167. if (className.includes("ant")) return className;
  168. },
  169. };
  170. }
  171. });
  172. }
  173. if (testString.includes(".css")) {
  174. rule.exclude = /tailwind\.css/;
  175. }
  176. });
  177. config.module.rules.push(
  178. {
  179. test: /\.svg$/,
  180. exclude: /node_modules/,
  181. use: [
  182. {
  183. loader: "@svgr/webpack",
  184. options: {
  185. ref: true,
  186. },
  187. },
  188. "url-loader",
  189. ],
  190. },
  191. {
  192. test: /\.xml$/,
  193. exclude: /node_modules/,
  194. loader: "url-loader",
  195. },
  196. {
  197. test: /\.wasm$/,
  198. type: "javascript/auto",
  199. loader: "file-loader",
  200. options: {
  201. name: "[name].[ext]",
  202. },
  203. },
  204. // tailwindcss
  205. {
  206. test: /tailwind\.css/,
  207. exclude: /node_modules/,
  208. use: [
  209. "style-loader",
  210. {
  211. loader: "css-loader",
  212. options: {
  213. importLoaders: 1,
  214. },
  215. },
  216. "postcss-loader",
  217. ],
  218. },
  219. );
  220. if (isDevelopment) {
  221. config.optimization = {
  222. ...config.optimization,
  223. moduleIds: "named",
  224. };
  225. }
  226. config.resolve.alias = {
  227. // Common dependencies across at least two sub-packages
  228. react: path.resolve(__dirname, "node_modules/react"),
  229. "react-dom": path.resolve(__dirname, "node_modules/react-dom"),
  230. "react-joyride": path.resolve(__dirname, "node_modules/react-joyride"),
  231. "@humansignal/ui": path.resolve(__dirname, "libs/ui"),
  232. "@humansignal/core": path.resolve(__dirname, "libs/core"),
  233. };
  234. return merge(config, {
  235. devtool,
  236. mode,
  237. plugins,
  238. optimization: optimizer(),
  239. devServer: process.env.MODE?.startsWith("standalone")
  240. ? {}
  241. : {
  242. // Port for the Webpack dev server
  243. port: HMR_PORT,
  244. // Enable HMR
  245. hot: true,
  246. // Allow cross-origin requests from Django
  247. headers: { "Access-Control-Allow-Origin": "*" },
  248. static: {
  249. directory: path.resolve(__dirname, "../label_studio/core/static/"),
  250. publicPath: "/static/",
  251. },
  252. devMiddleware: {
  253. publicPath: `${FRONTEND_HOSTNAME}/react-app/`,
  254. },
  255. allowedHosts: "all", // Allow access from Django's server
  256. proxy: [
  257. {
  258. context: ["/api"],
  259. target: `${DJANGO_HOSTNAME}/api`,
  260. changeOrigin: true,
  261. pathRewrite: { "^/api": "" },
  262. secure: false,
  263. },
  264. {
  265. context: ["/"],
  266. target: `${DJANGO_HOSTNAME}`,
  267. changeOrigin: true,
  268. secure: false,
  269. },
  270. ],
  271. },
  272. });
  273. },
  274. );