hatch-node-build


Namehatch-node-build JSON
Version 1.1.0 PyPI version JSON
download
home_pageNone
SummaryHatch build hook plugin for Node.js builds
upload_time2025-08-16 18:56:06
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseNone
keywords build hatch node nodejs plugin web build app
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Hatch Node Build

[![PyPI - Version](https://img.shields.io/pypi/v/hatch_node_build.svg)](https://pypi.org/project/hatch_node_build)

A plugin for [Hatch](https://github.com/pypa/hatch) that allows you to run Node.js builds and include the build
artifacts in your Python package.

## Installation

To set up `hatch-node-build`, set `hatchling` as your build backend, and add `hatch-node-build` as a
dependency, and as a Hatch build hook:

```toml
[build-system]
requires = ["hatchling", "hatch-node-build"]
build-backend = "hatchling.build"

[tool.hatch.build.hooks.node-build]
```

Including simply the table header as shown here is valid minimal TOML to enable the hook. Additional
[configuration](#build--build-configuration) of the plugin is added here as well.

## Usage

The plugin allows you to build a JavaScript bundle with `npm run build` (by default, see [configuration](#build--build-configuration)).
Include your Node assets in a folder called `/browser`, and it will be included in your Python package.

A minimal [React](https://react.dev/) app using [`esbuild`](https://esbuild.github.io/) would look as follows:

```
/
├─ my_python_module/
│  └─ __init__.py
└─ browser/
   ├─ src/
   │  └─ index.jsx
   └─ package.json
```

### `browser/package.json`

```json
{
  "name": "my-react-esbuild-app",
  "version": "1.0.0",
  "description": "A minimal React app using ESBuild",
  "type": "module",
  "scripts": {
    "build": "esbuild src/index.jsx --bundle --outfile=dist/bundle.js --format=iife --minify",
    "watch": "esbuild src/index.jsx --bundle --outfile=dist/bundle.js --format=iife --sourcemap --watch"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "esbuild": "^0.18.0"
  }
}
```

### `browser/src/index.jsx`

```js
import React from 'react';
import ReactDOM from 'react-dom/client';

const App = () => {
  return <h1>Hello, React with ESBuild in a Python package!</h1>;
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
```

After the build, the Python package would include `bundle/bundle.js`. See [Runtime](#runtime) for more information on
different ways to launch your JavaScript bundle when a user uses your package.

## Build & build configuration

The default build uses the `/browser` directory in your repository as the source directory, and bundles the output
(from the `/browser/dist` directory) in the output distribution in the `bundle` directory.

The steps of a build are as follows:

* Check `/browser/package.json` for Node.js requirements.
* Use Node.js found on PATH
  * If no matching version is found, install LTS version in cache.
* Run `npm install` in `/browser`
* Run `npm run build` in `/browser`
* Include `/browser/dist` in output as `/bundle`

The build can be configured in the following ways:

| Key               | Default           | Description                                                                           |
|-------------------|-------------------|---------------------------------------------------------------------------------------|
| `node_executable` | `"node"`          | Path to node executable to use.                                                       |
| `lts`             | `True`            | Only install LTS versions.                                                            |
| `install_command` | `"npm install"`   | The command to run to initialize the Node environment.                                |
| `build_command`   | `"npm run build"` | The command to run to build the artifacts.                                            |
| `source_dir`      | `"browser"`       | The location where the JavaScript source assets are stored.                           |
| `artifact_dir`    | `"dist"`          | The location relative to the source directory where the build artifacts end up.       |
| `bundle_dir`      | `"bundle"`        | The location in the Python package output the bundle should be included at.           |
| `inline_bundle`   | `False`           | Inline the JavaScript bundle `{artifact_dir}/bundle.js` in `{source_dir}/index.html`. |

If you need to make changes to the configuration, specify them in the build hook configuration:

```toml
[tool.hatch.build.hooks.node-build]
source_dir = "src/web"
bundle_dir = "web"
```

## Runtime

Using `hatch-node-build` you can include the bundle in your package, but that doesn't open the app yet when someone
uses your package.

There are a few things to consider, for example, the user's target machine might not have the Node.js runtime available,
so `hatch-node-build` works best, and focuses on frontend application bundles to be run in the browser.

Most web bundlers create bundles that span multiple files/chunks so that the web app can be loaded incrementally for
faster load times, and the bundle has to be served from an HTTP server. Spawning and controlling an HTTP server from
Python create challenging restrictions on the architecture of your code. It can be a lot simpler for small
applications to inline the JavaScript bundle in an HTML file and have the user's platform open it for you.

### Inlining the bundle

`hatch-node-build` supports simple inlining of your built bundle if the `inline_bundle` setting is turned on. It will look
for a file called `index.html` in the source directory, and will inline the JavaScript and CSS bundles into the placeholders:

```
/
├─ my_python_module/
│  └─ __init__.py
└─ browser/
   ├─ src/
   │  ├─ index.css
   │  └─ index.jsx
   │  index.html
   └─ package.json
```

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Sample React App</title>
    <style data-bundle-css></style>
  </head>
  <body>
    <div id="root"></div>
    <script data-bundle-js></script>
  </body>
</html>

```

Your Python package will then include `bundle/index.html` instead of `bundle/bundle.js`, and you can simply:

```python
import pathlib
import webbrowser

webbrowser.open(pathlib.Path(__file__).parent / "bundle" / "index.html")
```

Your code will not block and simply continue its execution.

> [!TIP]
> 
> If you want to check whether inlining worked, the `data-bundle-*` attribute will have been stripped.

### Running through an HTTP server

This is a simple example for an HTTP server. The Python process will be blocked while serving the HTTP server,
so you'll likely want a better and event-loop or async-based solution:

```python
import http.server
import socketserver
import os

PORT = 8000
web_dir = os.path.join(os.path.dirname(__file__), "static")
os.chdir(web_dir)

Handler = http.server.SimpleHTTPRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print(f"Serving at http://localhost:{PORT}")
    httpd.serve_forever()
```


            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "hatch-node-build",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "build, hatch, node, nodejs, plugin, web, build, app",
    "author": null,
    "author_email": "Robin De Schepper <robin@alexandria.sc>",
    "download_url": "https://files.pythonhosted.org/packages/06/70/b2cb2c7dcb07bd1c83495707e5ceb3aa359fd363122a53aef9c1cfed92e9/hatch_node_build-1.1.0.tar.gz",
    "platform": null,
    "description": "# Hatch Node Build\n\n[![PyPI - Version](https://img.shields.io/pypi/v/hatch_node_build.svg)](https://pypi.org/project/hatch_node_build)\n\nA plugin for [Hatch](https://github.com/pypa/hatch) that allows you to run Node.js builds and include the build\nartifacts in your Python package.\n\n## Installation\n\nTo set up `hatch-node-build`, set `hatchling` as your build backend, and add `hatch-node-build` as a\ndependency, and as a Hatch build hook:\n\n```toml\n[build-system]\nrequires = [\"hatchling\", \"hatch-node-build\"]\nbuild-backend = \"hatchling.build\"\n\n[tool.hatch.build.hooks.node-build]\n```\n\nIncluding simply the table header as shown here is valid minimal TOML to enable the hook. Additional\n[configuration](#build--build-configuration) of the plugin is added here as well.\n\n## Usage\n\nThe plugin allows you to build a JavaScript bundle with `npm run build` (by default, see [configuration](#build--build-configuration)).\nInclude your Node assets in a folder called `/browser`, and it will be included in your Python package.\n\nA minimal [React](https://react.dev/) app using [`esbuild`](https://esbuild.github.io/) would look as follows:\n\n```\n/\n\u251c\u2500 my_python_module/\n\u2502  \u2514\u2500 __init__.py\n\u2514\u2500 browser/\n   \u251c\u2500 src/\n   \u2502  \u2514\u2500 index.jsx\n   \u2514\u2500 package.json\n```\n\n### `browser/package.json`\n\n```json\n{\n  \"name\": \"my-react-esbuild-app\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A minimal React app using ESBuild\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"esbuild src/index.jsx --bundle --outfile=dist/bundle.js --format=iife --minify\",\n    \"watch\": \"esbuild src/index.jsx --bundle --outfile=dist/bundle.js --format=iife --sourcemap --watch\"\n  },\n  \"dependencies\": {\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\"\n  },\n  \"devDependencies\": {\n    \"esbuild\": \"^0.18.0\"\n  }\n}\n```\n\n### `browser/src/index.jsx`\n\n```js\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\n\nconst App = () => {\n  return <h1>Hello, React with ESBuild in a Python package!</h1>;\n};\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(<App />);\n```\n\nAfter the build, the Python package would include `bundle/bundle.js`. See [Runtime](#runtime) for more information on\ndifferent ways to launch your JavaScript bundle when a user uses your package.\n\n## Build & build configuration\n\nThe default build uses the `/browser` directory in your repository as the source directory, and bundles the output\n(from the `/browser/dist` directory) in the output distribution in the `bundle` directory.\n\nThe steps of a build are as follows:\n\n* Check `/browser/package.json` for Node.js requirements.\n* Use Node.js found on PATH\n  * If no matching version is found, install LTS version in cache.\n* Run `npm install` in `/browser`\n* Run `npm run build` in `/browser`\n* Include `/browser/dist` in output as `/bundle`\n\nThe build can be configured in the following ways:\n\n| Key               | Default           | Description                                                                           |\n|-------------------|-------------------|---------------------------------------------------------------------------------------|\n| `node_executable` | `\"node\"`          | Path to node executable to use.                                                       |\n| `lts`             | `True`            | Only install LTS versions.                                                            |\n| `install_command` | `\"npm install\"`   | The command to run to initialize the Node environment.                                |\n| `build_command`   | `\"npm run build\"` | The command to run to build the artifacts.                                            |\n| `source_dir`      | `\"browser\"`       | The location where the JavaScript source assets are stored.                           |\n| `artifact_dir`    | `\"dist\"`          | The location relative to the source directory where the build artifacts end up.       |\n| `bundle_dir`      | `\"bundle\"`        | The location in the Python package output the bundle should be included at.           |\n| `inline_bundle`   | `False`           | Inline the JavaScript bundle `{artifact_dir}/bundle.js` in `{source_dir}/index.html`. |\n\nIf you need to make changes to the configuration, specify them in the build hook configuration:\n\n```toml\n[tool.hatch.build.hooks.node-build]\nsource_dir = \"src/web\"\nbundle_dir = \"web\"\n```\n\n## Runtime\n\nUsing `hatch-node-build` you can include the bundle in your package, but that doesn't open the app yet when someone\nuses your package.\n\nThere are a few things to consider, for example, the user's target machine might not have the Node.js runtime available,\nso `hatch-node-build` works best, and focuses on frontend application bundles to be run in the browser.\n\nMost web bundlers create bundles that span multiple files/chunks so that the web app can be loaded incrementally for\nfaster load times, and the bundle has to be served from an HTTP server. Spawning and controlling an HTTP server from\nPython create challenging restrictions on the architecture of your code. It can be a lot simpler for small\napplications to inline the JavaScript bundle in an HTML file and have the user's platform open it for you.\n\n### Inlining the bundle\n\n`hatch-node-build` supports simple inlining of your built bundle if the `inline_bundle` setting is turned on. It will look\nfor a file called `index.html` in the source directory, and will inline the JavaScript and CSS bundles into the placeholders:\n\n```\n/\n\u251c\u2500 my_python_module/\n\u2502  \u2514\u2500 __init__.py\n\u2514\u2500 browser/\n   \u251c\u2500 src/\n   \u2502  \u251c\u2500 index.css\n   \u2502  \u2514\u2500 index.jsx\n   \u2502  index.html\n   \u2514\u2500 package.json\n```\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Sample React App</title>\n    <style data-bundle-css></style>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script data-bundle-js></script>\n  </body>\n</html>\n\n```\n\nYour Python package will then include `bundle/index.html` instead of `bundle/bundle.js`, and you can simply:\n\n```python\nimport pathlib\nimport webbrowser\n\nwebbrowser.open(pathlib.Path(__file__).parent / \"bundle\" / \"index.html\")\n```\n\nYour code will not block and simply continue its execution.\n\n> [!TIP]\n> \n> If you want to check whether inlining worked, the `data-bundle-*` attribute will have been stripped.\n\n### Running through an HTTP server\n\nThis is a simple example for an HTTP server. The Python process will be blocked while serving the HTTP server,\nso you'll likely want a better and event-loop or async-based solution:\n\n```python\nimport http.server\nimport socketserver\nimport os\n\nPORT = 8000\nweb_dir = os.path.join(os.path.dirname(__file__), \"static\")\nos.chdir(web_dir)\n\nHandler = http.server.SimpleHTTPRequestHandler\n\nwith socketserver.TCPServer((\"\", PORT), Handler) as httpd:\n    print(f\"Serving at http://localhost:{PORT}\")\n    httpd.serve_forever()\n```\n\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Hatch build hook plugin for Node.js builds",
    "version": "1.1.0",
    "project_urls": {
        "Documentation": "https://github.com/Helveg/hatch-node-build?tab=readme-ov-file#hatch-node-build",
        "History": "https://github.com/helveg/hatch-node-build/commits/main",
        "Issues": "https://github.com/helveg/hatch-node-build/issues",
        "Source": "https://github.com/helveg/hatch-node-build"
    },
    "split_keywords": [
        "build",
        " hatch",
        " node",
        " nodejs",
        " plugin",
        " web",
        " build",
        " app"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "7195e5876affb0d5ecd8d4056e1de826073de86f769161a9726478a9367eec55",
                "md5": "6369c7c947bac3182e5dafd7d955f9b1",
                "sha256": "914f0b0267f7d6104416ba690f1f334cfe2462291c8a75d323df4ecf1933cd46"
            },
            "downloads": -1,
            "filename": "hatch_node_build-1.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "6369c7c947bac3182e5dafd7d955f9b1",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 10940,
            "upload_time": "2025-08-16T18:56:04",
            "upload_time_iso_8601": "2025-08-16T18:56:04.366849Z",
            "url": "https://files.pythonhosted.org/packages/71/95/e5876affb0d5ecd8d4056e1de826073de86f769161a9726478a9367eec55/hatch_node_build-1.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "0670b2cb2c7dcb07bd1c83495707e5ceb3aa359fd363122a53aef9c1cfed92e9",
                "md5": "0a6b472b22a27df31fe991db32fed676",
                "sha256": "b248aa4a6b1282f7c7e17047981abee6154d33f7b0e79e5df477552200e24bd9"
            },
            "downloads": -1,
            "filename": "hatch_node_build-1.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "0a6b472b22a27df31fe991db32fed676",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 12524,
            "upload_time": "2025-08-16T18:56:06",
            "upload_time_iso_8601": "2025-08-16T18:56:06.265371Z",
            "url": "https://files.pythonhosted.org/packages/06/70/b2cb2c7dcb07bd1c83495707e5ceb3aa359fd363122a53aef9c1cfed92e9/hatch_node_build-1.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-16 18:56:06",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "Helveg",
    "github_project": "hatch-node-build?tab=readme-ov-file#hatch-node-build",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "hatch-node-build"
}
        
Elapsed time: 1.39830s