Webpack / Babel (in UX builds)

Modern Niagara UX modules are real front-end projects. The days of dropping a widget into a PX page are giving way to HTML5 interfaces built with proper tooling – and that means a build pipeline. Software Pile brings current front-end engineering practice to Niagara UX work: bundling, transpilation, and asset optimization for interfaces that load fast and behave well.

Why a Build Pipeline Matters in Niagara UX

Webpack and Babel let us write UX modules in modern JavaScript, share code cleanly, transpile for browser compatibility, and ship bundled, minified assets instead of a pile of unoptimized files. On a JACE serving dashboards to operators, that difference is felt in load time and responsiveness.

What We Build With It

  • HTML5 Niagara UX modules with a maintainable, buildable codebase
  • Shared component libraries reused across dashboards and sites
  • Optimized bundles sized for delivery from Supervisors and JACE controllers
  • Integration with BajaScript for live station data in the browser

Tell us about the interface you need and your Niagara version. Modern tooling means a UX you can maintain, not just one that looks good at handover.

One Widget or a Whole Toolchain

A single custom widget, a small Px adjustment, or a one-off view for one site can be delivered without introducing a toolchain that somebody then has to maintain for the life of the installation. Start by asking whether this project is one of those.

A build pipeline earns its place when code is shared across many views or many sites, when the interface is large enough to need components, when you want modern JavaScript on browsers that predate it, or when more than one person touches the code. If none of those apply, the recommendation is to stay with what Workbench gives you and revisit later. Niagara Graphics and Operator Dashboards covers the Px route in more depth.

Make the Bundle Part of the Module Build

The maintenance failure to design against is a bundle committed to source control while the sources that produced it live on somebody’s laptop. Six months later nobody can regenerate it.

The fix is to have Gradle drive the front-end build, so producing the module produces the bundle from source every time. A fresh checkout on a clean machine should build the module, and if it does not, the project has a problem that will surface at the worst possible moment.

Bundle Size Where the Host Is Constrained

A Supervisor on a corporate network and a JACE serving dashboards over a slow site link are different delivery environments. Code splitting, tree shaking and importing individual functions instead of whole libraries all help, and so does sensible cache behavior so a returning operator is not refetching everything.

What none of that supports is a general claim about how much faster a page becomes. Load time depends on the host, the network and the view, so the useful step is measuring on the actual target before and after.

Target the Browsers You Will Actually Meet

Operator workstations in plant rooms are not always current. Corporate images can be locked to an older browser, and some sites still run whatever was installed when the system was commissioned. The real browser inventory should drive the Babel target configuration.

Guessing costs you in both directions. Aim too modern and pages break on the machine the operator actually uses. Aim too conservative and you ship transpiled output and polyfills nobody needs. Ask the client which browsers are approved before configuring anything.

Debugging Long After Handover

Minified code is unreadable when something goes wrong on a Tuesday morning two years from now. A few habits make that survivable: publish source maps to a location your engineers can reach, version the bundle so the browser cannot serve a stale one after an update, and display a small build identifier somewhere in the interface.

That last one sounds trivial until the first support call. A bug report that names a build turns a guessing exercise into a lookup.

Dependency Discipline

Every package added to a UX module is something a future engineer has to update, audit or replace. Building systems stay in service far longer than front-end libraries stay fashionable, and a dependency tree assembled casually becomes a security review problem later.

The working rule is to keep the tree small, prefer libraries with a stable release history, pin versions with a committed lockfile, and record why each non-obvious dependency is there. The WebSockets work in UX modules follows the same principle.

Where the Bundle Lives Inside the Module

Niagara 4 splits a module by runtime profile, and browser facing JavaScript belongs to the ux profile: a module named myModule ships its browser code as myModule-ux.jar. Files placed under the module's rc directory are the ones the station will serve, addressed as module://myModule/rc/... in an ORD and reachable over the station's web service at the corresponding /module path. Webpack's output directory should therefore point into that source tree, so the bundle lands where the module packaging step already looks, rather than into a dist folder that somebody copies across by hand.

The consequence engineers notice first is that there is no file on the host to edit. The bundle is inside a JAR, the JAR is installed software, and changing one line of JavaScript means building the module again and installing it again. That is tolerable when the build is one command and miserable when it is not.

This also sets the granularity of everything downstream. Splitting a bundle into chunks is a decision about what the browser fetches, not about what gets deployed, because deployment is still whole JARs. Two views that always load together gain nothing from being separate chunks inside the same module.

Tell the Bundler What Niagara Already Loads

Niagara 4 loads browser code as AMD modules through RequireJS, with module IDs of the form nmodule/myModule/rc/myView. A build that emits a plain browser bundle with a global variable will not be loadable that way, so the output has to be produced as an AMD module and the entry point has to sit where that module ID resolves.

The other half of the configuration is externals. The station already serves baja, bajaux, jQuery and the lexicon support to every view in the shell. If those get pulled into your bundle instead of being declared external, the browser ends up holding two copies. Two copies of jQuery is wasted bytes. Two copies of baja is worse, because a subscription made through one instance is invisible to the other, and the symptom is a widget that renders correctly, shows plausible values once, and then never updates.

The diagnostic is to look at what the bundle actually contains rather than at what you meant to import. A bundler will report the modules it included and their sizes, and a library you assumed was external appears there by name. Confirming it in the browser is faster still: load a real view and check whether the same library arrives twice on the network panel, once from the station and once from your bundle.

  • baja, the BajaScript runtime the station already delivers to the shell
  • bajaux and the widget base classes your views extend
  • jQuery and anything else Niagara ships as part of the shell
  • the lexicon support used for localized text
  • anything already resolvable under an nmodule path, including other modules' resources

Getting a New Bundle Onto the Host

Delivery is a software install, not a file copy. The built JAR goes to the host through the Workbench software manager or an equivalent provisioning step, and installing module software brings a station restart with it. On an occupied building that restart is a scheduling conversation rather than a deployment detail, which is worth knowing before anyone treats a front end change as a quick fix.

Signing belongs in the same conversation. Recent Niagara 4 releases will not install a module the host does not consider signed by a trusted certificate, so a build server that produces the JAR either has access to that certificate or hands the artifact to whoever does. A pipeline that works on a developer machine and produces an uninstallable artifact in CI has usually stopped here.

Cache behavior has one Niagara specific wrinkle. The usual bundler answer is a content hash in the file name, but that file name is part of a module ID or a resource path that something else references as a fixed string, so hashing it means rewriting that reference in the same build. Where that is more machinery than the project wants, a build stamped folder under rc, or a version parameter carried on the reference, achieves the same result with less to go wrong.

  • Build the module from a clean checkout, front end included, as one command
  • Sign the resulting JAR with a certificate the target host trusts
  • Install through the software manager and take the station restart it triggers
  • Load a view that exercises live data, not just one that renders static markup

The Development Loop Against a Real Station

Hot module replacement does not reach inside a JAR, so the loop has to be built deliberately. What works is a watch build writing into the module's resource directory with a development server in front of it, proxying everything it does not serve to a real station, so that the shell, BajaScript and the login session come from Niagara while your JavaScript comes from disk. The proxy has to carry the session cookie through unchanged, or every data call returns unauthorized and the view looks broken for a reason that has nothing to do with the code you are editing.

Mocked data will carry you through layout and no further. Real points bring facets, engineering units, enum ranges, null and stale status, and permissions that differ by logged in user. A value that formats cleanly against a fixture and throws against a live subscription is the normal way this surfaces, so move onto a station with real points well before the interface is finished.

The station you iterate against does not have to be the controller you deploy to. A development station on a workstation class machine gives you a faster edit cycle, and the JACE is then where you verify size, load behavior and the browsers actually in use, rather than where you experiment.

Choosing the Bundler, and When Babel Alone Is Enough

Webpack is the general purpose choice and the one most people have configured before, which counts for something on a system somebody else will maintain in five years. It is not the only tool that fits. A shared component library that other modules consume is often better served by Rollup, which produces flatter output and cleaner AMD or UMD library builds. Where build speed is the complaint, esbuild and the tools built on it are dramatically faster, at the cost of a smaller plugin ecosystem when you eventually need something unusual.

There is also the case where Babel alone is enough. If the job is a handful of files that need transpiling for an older browser and there is nothing meaningful to bundle, Babel with no bundler in front of it is less to configure, less to explain in a handover document, and less to break. The arrangement genuinely worth avoiding is three overlapping tools where nobody can say which one applied a given transformation, because that is the configuration nobody wants to touch when a browser upgrade breaks a view.

Frequently Asked Questions

Are Webpack and Babel part of Niagara?

No. Both are general purpose tools from the Node ecosystem, installed per project and versioned in your own package.json. Niagara supplies the module format, the RequireJS loader, BajaScript and the bajaux widget framework, and it is indifferent to how the JavaScript inside your module was produced. The Px and Workbench route needs none of it at all. Where a pipeline is used, the tools themselves live in the module's source tree and its node_modules folder and never ship inside the JAR; only their output does.

What has to already exist before a build pipeline can be added?

A Niagara 4 module project with a working Gradle build, the Niagara development environment matching the version you are targeting, and Node and npm on whichever machine does the building. The HTML5 UX module structure this applies to is Niagara 4; Niagara AX served browser views through applets and Hx pages, which is a different structure that a bundle does not plug into. You also need a decision about who owns the repository after handover, because the build is now part of the deliverable rather than a developer convenience.

Does a build pipeline change how the module is installed or what operators see?

The install path is unchanged. It is still a module JAR installed through the software manager, the views appear at the same ORDs, and the station serves them the same way it serves any other module resource. What the pipeline changes lives in the source tree, in the build, and in the size and structure of what gets delivered to the browser.

How does bundled code get live station data?

Through BajaScript, which the station serves to the browser itself, which is why the bundle should reference it as an external rather than carry a copy. Values that need to keep changing arrive through subscriptions rather than one time reads, and a subscription consumes resources on the station as well as in the browser. A widget that subscribes and never releases will accumulate subscriptions across an operator session that runs all shift, so tie the release to the widget's destroy step. This one tends to present as a slow browser rather than as a bug, which is how it survives testing.

What does the client have to decide before anything gets configured?

Four things, and they are all cheap to answer early and expensive to change late. The Niagara version and the host that will serve the interface. The approved browser list, which drives the Babel configuration. Whether the interface is meant to be reused on other sites, because that is the difference between building one module and building a shared component library. And who holds the repository, the lockfile and the signing certificate once the project closes.

Can a build pipeline be added to a UX module that already exists?

Yes, and it usually goes better incrementally than as a rewrite. Leave the existing files being served as they are, point the build at new code first, and migrate views one at a time once the loop is proven. What makes this hard is rarely the tooling: it is files that were edited on the host or inside an unpacked JAR and never went back into source, and code that quietly depends on load order because everything shares the global scope. Find both before you scope the migration, because they set the real size of the job.

Does the bundle need attention when Niagara is upgraded?

Yes, in two specific places. The module manifest declares its dependencies with minimum versions, and a station will refuse to load a module whose declared dependencies it cannot satisfy, so an upgrade is the moment those get tested. Separately, everything you declared external is now whatever the new release ships, so a platform side move in jQuery or bajaux reaches your code without you rebuilding anything. After any upgrade, exercise a view that does real subscription work rather than one that only renders markup.