Skip to content

Commit

Permalink
chore: OPTIC-1060: Hot Module Reload support for frontend development (
Browse files Browse the repository at this point in the history
…#6280)

Co-authored-by: Yousif Yassi <yousif@heartex.com>
Co-authored-by: yyassi-heartex <yyassi-heartex@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 6, 2024
1 parent 9f4a164 commit c5696cd
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 32 deletions.
4 changes: 4 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FRONTEND_HMR=true
FRONTEND_HOSTNAME=http://localhost:8010
DJANGO_HOSTNAME=http://localhost:8080

28 changes: 22 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,37 @@ makemigrations-dev:
shell-dev:
DJANGO_DB=sqlite LOG_DIR=tmp DEBUG=true LOG_LEVEL=DEBUG DJANGO_SETTINGS_MODULE=core.settings.label_studio python label_studio/manage.py shell_plus

env-dev-setup:
if [ ! -f .env ]; then \
cp .env.development .env; \
fi

docker-dev-override:
if [ ! -f docker-compose.override.yml ]; then \
cp docker-compose.override.example.yml docker-compose.override.yml; \
fi

# Configure Django dev server with Hot Module Replacement in docker
docker-dev-setup: env-dev-setup docker-dev-override

docker-run-dev:
docker-compose up --build

docker-migrate-dev:
docker-compose run app python3 /label-studio/label_studio/manage.py migrate


# Install modules
frontend-setup:
frontend-install:
cd web && yarn install --frozen-lockfile;

# Keep it here for potential rollback
## Fetch DM and LSF
#frontend-fetch:
# cd label_studio/frontend && yarn run download:all;
# Alias for backward compatibility
frontend-setup: frontend-install

# Run frontend dev server in Hot Module Replacement mode
# For more information on HMR, see the "Environment Configuration" section in:
# web/README.md
frontend-dev:
cd web && yarn run dev

# Build frontend continuously on files changes
frontend-watch:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ You can deploy Label Studio with one click in Heroku, Microsoft Azure, or Google

#### Apply frontend changes

For information about updating the frontend, see [label-studio/web/README.md](https://github.com/HumanSignal/label-studio/blob/develop/web/README.md#usage-instructions).
For information about updating the frontend, see [label-studio/web/README.md](https://github.com/HumanSignal/label-studio/blob/develop/web/README.md#installation-instructions).


#### Install dependencies on Windows
Expand Down
5 changes: 5 additions & 0 deletions docker-compose.override.example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version: "3.9"
services:
app:
env_file:
- .env
7 changes: 5 additions & 2 deletions label_studio/core/settings/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""This file and its contents are licensed under the Apache License 2.0. Please see the included NOTICE for copyright information and LICENSE for a copy of the license.
"""
"""This file and its contents are licensed under the Apache License 2.0. Please see the included NOTICE for copyright information and LICENSE for a copy of the license."""

"""
Django Base settings for Label Studio.
Expand Down Expand Up @@ -104,6 +104,9 @@
if FORCE_SCRIPT_NAME:
logger.info('=> Django URL prefix is set to: %s', FORCE_SCRIPT_NAME)

FRONTEND_HMR = get_bool_env('FRONTEND_HMR', False)
FRONTEND_HOSTNAME = get_env('FRONTEND_HOSTNAME', 'http://localhost:8010' if FRONTEND_HMR else HOSTNAME)

DOMAIN_FROM_REQUEST = get_bool_env('DOMAIN_FROM_REQUEST', False)

if DOMAIN_FROM_REQUEST:
Expand Down
8 changes: 4 additions & 4 deletions label_studio/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<link href="{{settings.HOSTNAME}}{% static 'images/favicon.ico' %}" rel="shortcut icon"/>
<link href="{{settings.HOSTNAME}}{% static 'css/uikit.css' %}" rel="stylesheet">
<link href="{{settings.HOSTNAME}}{% static 'css/main.css' %}" rel="stylesheet">
<link href="{{settings.HOSTNAME}}/react-app/main.css?v={{ versions.backend.commit }}" rel="stylesheet">
<link href="{{settings.FRONTEND_HOSTNAME}}/react-app/main.css?v={{ versions.backend.commit }}" rel="stylesheet">

{% block app-scripts %}
{% endblock %}
Expand Down Expand Up @@ -112,13 +112,13 @@
</script>

{% block app-js %}
<script src="{{settings.HOSTNAME}}/react-app/runtime.js?v={{ versions.backend.commit }}"></script>
<script src="{{settings.FRONTEND_HOSTNAME}}/react-app/runtime.js?v={{ versions.backend.commit }}"></script>
{% comment %}
NOTE: purposely setting this to not cache using backend commit as we do not intend this to change frequently.
If for any reason we need to invalidate the cache, we can do so by changing the version number.
{% endcomment %}
<script src="{{settings.HOSTNAME}}/react-app/vendor.js?v=1"></script>
<script src="{{settings.HOSTNAME}}/react-app/main.js?v={{ versions.backend.commit }}"></script>
<script src="{{settings.FRONTEND_HOSTNAME}}/react-app/vendor.js?v=1"></script>
<script src="{{settings.FRONTEND_HOSTNAME}}/react-app/main.js?v={{ versions.backend.commit }}"></script>
{% endblock %}

{% block bottomjs %}
Expand Down
2 changes: 1 addition & 1 deletion label_studio/templates/simple.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<link href="{{settings.HOSTNAME}}{% static 'images/favicon.ico' %}" rel="shortcut icon"/>
<link href="{{settings.HOSTNAME}}{% static 'css/main.css' %}" rel="stylesheet"/>
<link href="{{settings.HOSTNAME}}/react-app/main.css?v={{ versions.backend.commit }}" rel="stylesheet">
<link href="{{settings.FRONTEND_HOSTNAME}}/react-app/main.css?v={{ versions.backend.commit }}" rel="stylesheet">

{% block head %}
{% endblock %}
Expand Down
21 changes: 19 additions & 2 deletions web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,35 @@ Datamanager is an advanced tool specifically for data exploration within Label S
1 - **Dependencies Installation:**
- Execute `yarn install --frozen-lockfile` to install all necessary dependencies.

2 - **Environment Configuration:**
2 - **Environment Configuration (Optional for HMR):**
- If you want to enable Hot Module Replacement (HMR), create an `.env` file in the root Label Studio directory.
- Add the following configuration:
- `FRONTEND_HMR=true`: Enables Hot Module Replacement in Django.

Optional configurations (defaults should work for most setups):
- `FRONTEND_HOSTNAME`: HMR server address (default: http://localhost:8010).
- `DJANGO_HOSTNAME`: Django server address (default: http://localhost:8080).

If using Docker Compose with HMR:
- Update the `env_file: .env` directive in `docker-compose.override.yml` under the app service.
- Rerun the app or docker compose service from the project root for changes to take effect.

To start the development server with HMR:
- From the `web` directory: Run `yarn dev`
- Or from the project root: Run `make frontend-dev`

#### Custom Configuration for DataManager:
- If you need to customize the configuration specifically for DataManager, follow these steps:
- Duplicate the `.env.example` file located in the DataManager directory and rename the copy to `.env`.
- Make your desired changes in this new `.env` file. The key configurations to consider are:
- `NX_API_GATEWAY`: Set this to your API root. For example, `https://localhost:8080/api/dm`.
- `NX_API_GATEWAY`: Set this to your API root. For example, `http://localhost:8080/api/dm`.
- `LS_ACCESS_TOKEN`: This is the access token for Label Studio, which can be obtained from your Label Studio account page.
- This process allows you to have a customized configuration for DataManager, separate from the default settings in the .env.local files.

## Usage Instructions
### Key Development and Build Commands
- **Label Studio App:**
- `yarn ls:dev`: Build the main Label Studio app with Hot Module Reload for development.
- `yarn ls:watch`: Build the main Label Studio app continuously for development.
- `yarn ls:e2e`: Run end-to-end tests for the Label Studio app.
- `yarn ls:unit`: Run unit tests for the Label Studio app.
Expand Down
4 changes: 4 additions & 0 deletions web/apps/labelstudio/src/app/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,7 @@ const root = document.querySelector(".app-wrapper");
const content = document.querySelector("#main-content");

render(<App content={content.innerHTML} />, root);

if (module?.hot) {
module.hot.accept(); // Enable HMR for React components
}
6 changes: 4 additions & 2 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ui:serve": "nx storybook storybook",
"ui:serve": "FRONTEND_HOSTNAME=http://localhost:3000 nx storybook storybook",
"ui:test:component": "nx run storybook:test-component",
"lint": "biome check --apply .",
"lint-scss": "yarn stylelint '**/*.scss' --fix",
"ls:dev": "nx run labelstudio:serve:development",
"ls:watch": "nx run labelstudio:build:development --watch",
"ls:build": "nx run labelstudio:build:production",
"ls:unit": "nx run labelstudio:unit",
"ls:e2e": "nx run labelstudio-e2e:e2e",
"lsf:watch": "nx run editor:build:development --watch",
"lsf:serve": "MODE=standalone nx run editor:serve:development",
"lsf:serve": "FRONTEND_HOSTNAME=http://localhost:3000 MODE=standalone nx run editor:serve:development",
"lsf:e2e": "cd libs/editor/tests/e2e && yarn test",
"lsf:integration": "nx run editor:integration",
"lsf:integration:watch": "nx run editor:integration --watch",
Expand All @@ -23,6 +24,7 @@
"version:libs": "nx run-many --target=version",
"docs": "nx run-many --target=docs",
"watch": "NODE_ENV=development BUILD_NO_SERVER=true yarn ls:watch",
"dev": "NODE_ENV=development BUILD_NO_SERVER=true yarn ls:dev",
"test:e2e": "yarn ls:e2e && yarn lsf:e2e",
"test:integration": "yarn lsf:integration",
"test:unit": "nx run-many --target=unit",
Expand Down
58 changes: 44 additions & 14 deletions web/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ const { composePlugins, withNx } = require("@nx/webpack");
const { withReact } = require("@nx/react");
const { merge } = require("webpack-merge");

require("dotenv").config();
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");
Expand All @@ -14,29 +17,23 @@ 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_HOSTNAME = process.env.FRONTEND_HOSTNAME || "http://localhost:8010";
const DJANGO_HOSTNAME = process.env.DJANGO_HOSTNAME || "http://localhost:8080";
const HMR_PORT = +new URL(FRONTEND_HOSTNAME).port;

const LOCAL_ENV = {
NODE_ENV: mode,
CSS_PREFIX: css_prefix,
RELEASE_NAME: RELEASE,
};

const devtool = process.env.NODE_ENV === "production" ? "source-map" : "cheap-module-source-map";

const isDevelopment = mode !== "production";
const customDistDir = !!process.env.WORK_DIR;

const BUILD = {
NO_MINIMIZE: isDevelopment || !!process.env.BUILD_NO_MINIMIZATION,
};

const dirPrefix = {
js: customDistDir ? "js/" : isDevelopment ? "" : "static/js/",
css: customDistDir ? "css/" : isDevelopment ? "" : "static/css/",
};

const plugins = [
new MiniCssExtractPlugin(),
new DefinePlugin({
Expand Down Expand Up @@ -92,10 +89,11 @@ module.exports = composePlugins(
import: path.resolve(__dirname, "apps/labelstudio/src/main.tsx"),
},
};

config.output = {
...config.output,
uniqueName: "labelstudio",
publicPath: "auto",
publicPath: isDevelopment && FRONTEND_HOSTNAME ? `${FRONTEND_HOSTNAME}/react-app/` : "auto",
scriptType: "text/javascript",
};

Expand Down Expand Up @@ -222,16 +220,48 @@ module.exports = composePlugins(
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: dirPrefix.js, // colocate wasm with js
},
},
);

if (isDevelopment) {
config.optimization = {
...config.optimization,
moduleIds: "named",
};
}

return merge(config, {
devtool,
mode,
plugins,
optimization: optimizer(),
devServer:
process.env.MODE === "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: [
{
router: {
"/api": `${DJANGO_HOSTNAME}/api`, // Proxy api requests to Django's server
},
},
],
},
});
},
);

0 comments on commit c5696cd

Please sign in to comment.