Skip to main content

Hatch build hook plugin for Node.js builds

Project description

Hatch Node Build

PyPI - Version

A plugin for 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:

[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 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). Include your Node assets in a folder called /browser, and it will be included in your Python package.

A minimal React app using esbuild would look as follows:

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

browser/package.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

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 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:

[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
<!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:

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:

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()

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hatch_node_build-1.1.0.tar.gz (12.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hatch_node_build-1.1.0-py3-none-any.whl (10.9 kB view details)

Uploaded Python 3

File details

Details for the file hatch_node_build-1.1.0.tar.gz.

File metadata

  • Download URL: hatch_node_build-1.1.0.tar.gz
  • Upload date:
  • Size: 12.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-requests/2.32.3

File hashes

Hashes for hatch_node_build-1.1.0.tar.gz
Algorithm Hash digest
SHA256 b248aa4a6b1282f7c7e17047981abee6154d33f7b0e79e5df477552200e24bd9
MD5 0a6b472b22a27df31fe991db32fed676
BLAKE2b-256 0670b2cb2c7dcb07bd1c83495707e5ceb3aa359fd363122a53aef9c1cfed92e9

See more details on using hashes here.

File details

Details for the file hatch_node_build-1.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for hatch_node_build-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 914f0b0267f7d6104416ba690f1f334cfe2462291c8a75d323df4ecf1933cd46
MD5 6369c7c947bac3182e5dafd7d955f9b1
BLAKE2b-256 7195e5876affb0d5ecd8d4056e1de826073de86f769161a9726478a9367eec55

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page