// const path = require('path');
|
const path = require("path");
|
const { composePlugins, withNx } = require("@nx/webpack");
|
const { withReact } = require("@nx/react");
|
const { merge } = require("webpack-merge");
|
|
require("dotenv").config({
|
// resolve the .env file in the root of the project ../
|
path: path.resolve(__dirname, "../.env"),
|
});
|
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const { EnvironmentPlugin, DefinePlugin, ProgressPlugin, optimize } = require("webpack");
|
const TerserPlugin = require("terser-webpack-plugin");
|
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
|
|
const RELEASE = require("./release").getReleaseName();
|
|
const css_prefix = "lsf-";
|
const mode = process.env.BUILD_MODULE ? "production" : process.env.NODE_ENV || "development";
|
const isDevelopment = mode !== "production";
|
const devtool = process.env.NODE_ENV === "production" ? "source-map" : "cheap-module-source-map";
|
const FRONTEND_HMR = process.env.FRONTEND_HMR === "true";
|
const FRONTEND_HOSTNAME = FRONTEND_HMR ? process.env.FRONTEND_HOSTNAME || "http://localhost:8010" : "";
|
const DJANGO_HOSTNAME = process.env.DJANGO_HOSTNAME || "http://localhost:8080";
|
const HMR_PORT = FRONTEND_HMR ? +new URL(FRONTEND_HOSTNAME).port : 8010;
|
|
const LOCAL_ENV = {
|
NODE_ENV: mode,
|
CSS_PREFIX: css_prefix,
|
RELEASE_NAME: RELEASE,
|
};
|
|
const BUILD = {
|
NO_MINIMIZE: isDevelopment || !!process.env.BUILD_NO_MINIMIZATION,
|
};
|
|
const plugins = [
|
new MiniCssExtractPlugin(),
|
new DefinePlugin({
|
"process.env.CSS_PREFIX": JSON.stringify(css_prefix),
|
}),
|
new EnvironmentPlugin(LOCAL_ENV),
|
];
|
|
const optimizer = () => {
|
const result = {
|
minimize: true,
|
minimizer: [],
|
};
|
|
if (mode === "production") {
|
result.minimizer.push(
|
new TerserPlugin({
|
parallel: true,
|
}),
|
new CssMinimizerPlugin({
|
parallel: true,
|
}),
|
);
|
}
|
|
if (BUILD.NO_MINIMIZE) {
|
result.minimize = false;
|
result.minimizer = undefined;
|
}
|
|
if (process.env.MODE?.startsWith("standalone")) {
|
result.runtimeChunk = false;
|
result.splitChunks = { cacheGroups: { default: false } };
|
}
|
|
return result;
|
};
|
|
// Nx plugins for webpack.
|
module.exports = composePlugins(
|
withNx({
|
nx: {
|
svgr: true,
|
},
|
skipTypeChecking: true,
|
}),
|
withReact({ svgr: true }),
|
(config) => {
|
// LS entrypoint
|
if (!process.env.MODE?.startsWith("standalone")) {
|
config.entry = {
|
main: {
|
import: path.resolve(__dirname, "apps/labelstudio/src/main.tsx"),
|
},
|
};
|
|
config.output = {
|
...config.output,
|
uniqueName: "labelstudio",
|
publicPath:
|
isDevelopment && FRONTEND_HOSTNAME
|
? `${FRONTEND_HOSTNAME}/react-app/`
|
: process.env.MODE === "standalone-playground"
|
? "/playground-assets/"
|
: "auto",
|
scriptType: "text/javascript",
|
};
|
|
config.optimization = {
|
runtimeChunk: "single",
|
sideEffects: true,
|
splitChunks: {
|
cacheGroups: {
|
commonVendor: {
|
test: /[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom|mobx|mobx-react|mobx-react-lite|mobx-state-tree)[\\/]/,
|
name: "vendor",
|
chunks: "all",
|
},
|
defaultVendors: {
|
test: /[\\/]node_modules[\\/]/,
|
priority: -10,
|
reuseExistingChunk: true,
|
chunks: "async",
|
},
|
default: {
|
minChunks: 2,
|
priority: -20,
|
reuseExistingChunk: true,
|
chunks: "async",
|
},
|
},
|
},
|
};
|
}
|
|
config.resolve.fallback = {
|
fs: false,
|
path: false,
|
crypto: false,
|
worker_threads: false,
|
};
|
|
config.experiments = {
|
cacheUnaffected: true,
|
syncWebAssembly: true,
|
asyncWebAssembly: true,
|
};
|
|
config.module.rules.forEach((rule) => {
|
const testString = rule.test.toString();
|
const isScss = testString.includes("scss");
|
const isCssModule = testString.includes(".module");
|
|
if (isScss) {
|
rule.oneOf.forEach((loader) => {
|
if (loader.use) {
|
const cssLoader = loader.use.find((use) => use.loader && use.loader.includes("css-loader"));
|
|
if (cssLoader && cssLoader.options) {
|
cssLoader.options.modules = {
|
mode: "local",
|
auto: true,
|
namedExport: false,
|
localIdentName: "[local]--[hash:base64:5]",
|
};
|
}
|
}
|
});
|
}
|
|
if (rule.test.toString().match(/scss|sass/) && !isCssModule) {
|
const r = rule.oneOf.filter((r) => {
|
// we don't need rules that don't have loaders
|
if (!r.use) return false;
|
|
const testString = r.test.toString();
|
|
// we also don't need css modules as these are used directly
|
// in the code and don't need prefixing
|
if (testString.match(/module|raw|antd/)) return false;
|
|
// we only target pre-processors that has 'css-loader included'
|
return testString.match(/scss|sass/) && r.use.some((u) => u.loader && u.loader.includes("css-loader"));
|
});
|
|
r.forEach((_r) => {
|
const cssLoader = _r.use.find((use) => use.loader && use.loader.includes("css-loader"));
|
|
if (!cssLoader) return;
|
|
const isSASS = _r.use.some((use) => use.loader && use.loader.match(/sass|scss/));
|
|
if (isSASS) _r.exclude = /node_modules/;
|
|
if (cssLoader.options) {
|
cssLoader.options.modules = {
|
localIdentName: `${css_prefix}[local]`, // Customize this format
|
getLocalIdent(_ctx, _ident, className) {
|
if (className.includes("ant")) return className;
|
},
|
};
|
}
|
});
|
}
|
|
if (testString.includes(".css")) {
|
rule.exclude = /tailwind\.css/;
|
}
|
});
|
|
config.module.rules.push(
|
{
|
test: /\.svg$/,
|
exclude: /node_modules/,
|
use: [
|
{
|
loader: "@svgr/webpack",
|
options: {
|
ref: true,
|
},
|
},
|
"url-loader",
|
],
|
},
|
{
|
test: /\.xml$/,
|
exclude: /node_modules/,
|
loader: "url-loader",
|
},
|
{
|
test: /\.wasm$/,
|
type: "javascript/auto",
|
loader: "file-loader",
|
options: {
|
name: "[name].[ext]",
|
},
|
},
|
// tailwindcss
|
{
|
test: /tailwind\.css/,
|
exclude: /node_modules/,
|
use: [
|
"style-loader",
|
{
|
loader: "css-loader",
|
options: {
|
importLoaders: 1,
|
},
|
},
|
"postcss-loader",
|
],
|
},
|
);
|
|
if (isDevelopment) {
|
config.optimization = {
|
...config.optimization,
|
moduleIds: "named",
|
};
|
}
|
|
config.resolve.alias = {
|
// Common dependencies across at least two sub-packages
|
react: path.resolve(__dirname, "node_modules/react"),
|
"react-dom": path.resolve(__dirname, "node_modules/react-dom"),
|
"react-joyride": path.resolve(__dirname, "node_modules/react-joyride"),
|
"@humansignal/ui": path.resolve(__dirname, "libs/ui"),
|
"@humansignal/core": path.resolve(__dirname, "libs/core"),
|
};
|
|
return merge(config, {
|
devtool,
|
mode,
|
plugins,
|
optimization: optimizer(),
|
devServer: process.env.MODE?.startsWith("standalone")
|
? {}
|
: {
|
// Port for the Webpack dev server
|
port: HMR_PORT,
|
// Enable HMR
|
hot: true,
|
// Allow cross-origin requests from Django
|
headers: { "Access-Control-Allow-Origin": "*" },
|
static: {
|
directory: path.resolve(__dirname, "../label_studio/core/static/"),
|
publicPath: "/static/",
|
},
|
devMiddleware: {
|
publicPath: `${FRONTEND_HOSTNAME}/react-app/`,
|
},
|
allowedHosts: "all", // Allow access from Django's server
|
proxy: [
|
{
|
context: ["/api"],
|
target: `${DJANGO_HOSTNAME}/api`,
|
changeOrigin: true,
|
pathRewrite: { "^/api": "" },
|
secure: false,
|
},
|
{
|
context: ["/"],
|
target: `${DJANGO_HOSTNAME}`,
|
changeOrigin: true,
|
secure: false,
|
},
|
],
|
},
|
});
|
},
|
);
|