mirror of
https://github.com/hanzoai/gui.git
synced 2026-10-25 02:31:41 +00:00
The loader and the Next plugin only existed to drive webpack; Next runs on Turbopack and Vite has its own plugin. kitchen-sink and the integration app serve through vite, the optimizer suite drops the webpack name, and its fixture packages join the workspace so a fresh checkout links @my/ui.
545 lines
16 KiB
Plaintext
545 lines
16 KiB
Plaintext
---
|
|
title: Gui Compiler
|
|
description: Adding the compiler to your apps
|
|
---
|
|
|
|
<IntroParagraph>
|
|
The Gui Compiler significantly improves performance of both web and native applications
|
|
through partial analysis and view flattening.
|
|
</IntroParagraph>
|
|
|
|
See the [Benchmarks](/docs/intro/benchmarks) or a more
|
|
[in-depth background](/docs/intro/why-a-compiler). Note that Gui features
|
|
work at compile-time and runtime, so installing the compiler is optional, and in
|
|
fact we recommend only setting it up once you're ready for production.
|
|
|
|
The compiler uses Babel to analyze JSX and `styled` functions, then attempts to
|
|
statically analyze and optimize them down to platform-native primitives.
|
|
The end result is less abstraction - like a `div` on web, or plain React Native
|
|
`View` on native:
|
|
|
|
<br />
|
|
<br />
|
|
<GuiExamplesCode />
|
|
|
|
<Notice theme="blue">
|
|
The compiler generates built versions of your components and config into a `.hanzogui`
|
|
directory. You'll want to add that directory to your `.gitignore`.
|
|
</Notice>
|
|
|
|
## Configuration with hanzogui.build.ts
|
|
|
|
We recommend creating a `hanzogui.build.ts` file in your project root as the
|
|
single source of truth for your compiler configuration. All bundler plugins and
|
|
the CLI automatically read from this file, so you only need to define your
|
|
options once.
|
|
|
|
This file lets the Gui CLI read your config and perform operations like
|
|
generating CSS, pre-compiling components, and verifying optimizations — while
|
|
also sharing that same configuration with whichever bundler plugin you use.
|
|
Without it, you'd need to duplicate options across your metro, babel and vite
|
|
configs.
|
|
|
|
```ts fileName="hanzogui.build.ts"
|
|
import type { GuiBuildOptions } from '@hanzo/gui'
|
|
|
|
export default {
|
|
config: './hanzogui.config.ts',
|
|
components: ['@hanzo/gui'],
|
|
outputCSS: './public/hanzogui.generated.css',
|
|
// optional:
|
|
importsWhitelist: ['constants.js', 'colors.js'],
|
|
disableExtraction: process.env.NODE_ENV === 'development',
|
|
} satisfies GuiBuildOptions
|
|
```
|
|
|
|
Some notes on the options:
|
|
|
|
- _importsWhitelist_: Gui takes a conservative approach to partial
|
|
evaluation, this field whitelists (matching against both .ts and .js) files to
|
|
allow files that import them to read and use their values during compilation.
|
|
Typically colors and constants files.
|
|
- _disableExtraction_: Useful for faster developer iteration as your design
|
|
system hot reloads more reliably.
|
|
|
|
With this file in place, your bundler plugins can be configured with no options
|
|
at all — they'll pick up everything from `hanzogui.build.ts`:
|
|
|
|
```tsx
|
|
// vite: hanzoguiPlugin()
|
|
// metro: withGui(config)
|
|
// all read from hanzogui.build.ts automatically
|
|
```
|
|
|
|
You can still pass options directly to a plugin, and they'll be merged with
|
|
(and override) the options from `hanzogui.build.ts`.
|
|
|
|
## Install
|
|
|
|
There are plugins for a variety of bundlers, or you can use the `@hanzogui/cli`
|
|
to compile in-place:
|
|
|
|
### Vite
|
|
|
|
See the [Vite guide](/docs/guides/vite) for more complete setup.
|
|
|
|
<Notice theme="yellow">
|
|
`@hanzogui/vite-plugin` is ESM-only. Your project must have `"type": "module"` in its
|
|
`package.json` (or use `.mjs`/`.mts` config files).
|
|
</Notice>
|
|
|
|
Add `@hanzogui/vite-plugin` and update your `vite.config.ts`. If you have a
|
|
`hanzogui.build.ts`, no options are needed:
|
|
|
|
```tsx
|
|
import { hanzoguiPlugin } from '@hanzogui/vite-plugin'
|
|
|
|
export default defineConfig({
|
|
plugins: [
|
|
// reads from hanzogui.build.ts automatically
|
|
hanzoguiPlugin(),
|
|
],
|
|
})
|
|
```
|
|
|
|
Or pass options inline:
|
|
|
|
```tsx
|
|
import { hanzoguiPlugin } from '@hanzogui/vite-plugin'
|
|
|
|
export default defineConfig({
|
|
plugins: [
|
|
hanzoguiPlugin({
|
|
config: 'src/hanzogui.config.ts',
|
|
components: ['@hanzo/gui'],
|
|
disableExtraction: true,
|
|
}),
|
|
],
|
|
})
|
|
```
|
|
|
|
### Next.js
|
|
|
|
See the [guide](/docs/guides/next-js) for more complete setup.
|
|
|
|
Next.js runs on Turbopack, which needs no plugin: create a `hanzogui.build.ts`
|
|
and run `hanzogui build` ahead of `next build`.
|
|
|
|
```json fileName="package.json"
|
|
{
|
|
"scripts": {
|
|
"build": "hanzogui build --target web ./src -- next build"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Babel / Metro
|
|
|
|
Note that the `@hanzogui/babel-plugin` is completely optional, and on native
|
|
Gui doesn't optimize as much as on web, so leaving it out is actually
|
|
recommended to start. If later on you feel the need for a bit more speed, you
|
|
can try adding it.
|
|
|
|
```bash
|
|
yarn add @hanzogui/babel-plugin
|
|
```
|
|
|
|
Add to your `babel.config.js`. With a `hanzogui.build.ts`, you can pass no
|
|
options:
|
|
|
|
```js
|
|
module.exports = {
|
|
plugins: [
|
|
// reads from hanzogui.build.ts automatically
|
|
'@hanzogui/babel-plugin',
|
|
],
|
|
}
|
|
```
|
|
|
|
Or pass options inline:
|
|
|
|
```js
|
|
module.exports = {
|
|
plugins: [
|
|
[
|
|
'@hanzogui/babel-plugin',
|
|
{
|
|
components: ['@hanzo/gui'],
|
|
config: './hanzogui.config.ts',
|
|
importsWhitelist: ['constants.js', 'colors.js'],
|
|
logTimings: true,
|
|
disableExtraction: process.env.NODE_ENV === 'development',
|
|
},
|
|
],
|
|
],
|
|
}
|
|
```
|
|
|
|
### Expo
|
|
|
|
[Check out the Expo guide](/docs/guides/expo) for more information on setting up
|
|
Expo. It's as simple as adding the babel plugin.
|
|
|
|
### CLI-Based In-Place Compilation
|
|
|
|
For bundlers that don't have a Gui plugin yet (like Turbopack), or if you
|
|
prefer a simple setup, you can use `@hanzogui/cli` to pre-compile your components
|
|
in-place before your build step.
|
|
|
|
This approach is meant for **production builds only** and should run in your
|
|
deployment pipeline, not during development. It rewrites files in place which
|
|
will mess up your working directory, but makes it highly compatible with any
|
|
bundler or tool. The downside is you don't get the helpful development
|
|
compatibility parts of the plugins, plus dev-mode debugging and `data-`
|
|
attributes.
|
|
|
|
For complete CLI documentation including all available commands, see the
|
|
[CLI Guide](/docs/guides/cli).
|
|
|
|
#### Setup
|
|
|
|
1. Install:
|
|
|
|
```bash
|
|
yarn add -D @hanzogui/cli
|
|
```
|
|
|
|
2. Create a `hanzogui.build.ts` if you haven't already (see
|
|
[above](#configuration-with-hanzoguibuildts)).
|
|
|
|
3. Add a build script to your `package.json`:
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"build": "hanzogui build ./src -- next build"
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Usage
|
|
|
|
```bash
|
|
# Build all components in a directory (web + native by default)
|
|
npx hanzogui build ./src
|
|
|
|
# Build for web only
|
|
npx hanzogui build --target web ./src
|
|
|
|
# Build for native only
|
|
npx hanzogui build --target native ./src
|
|
|
|
# Build a specific file
|
|
npx hanzogui build ./src/components/MyComponent.tsx
|
|
|
|
# Include/exclude patterns
|
|
npx hanzogui build --include "components/**" --exclude "**/*.test.tsx" ./src
|
|
|
|
# Output to a separate directory (source files unchanged)
|
|
npx hanzogui build --output ./dist ./src
|
|
|
|
# Create platform-specific files next to source files (.web.tsx or .native.tsx)
|
|
npx hanzogui build --target native --output-around ./src
|
|
|
|
# Preview changes without writing files
|
|
npx hanzogui build --dry-run ./src
|
|
|
|
# Verify minimum optimizations (useful in CI)
|
|
npx hanzogui build --target web --expect-optimizations 10 ./src
|
|
```
|
|
|
|
#### CI Verification with --expect-optimizations
|
|
|
|
The `--expect-optimizations` flag ensures your build is actually optimizing components. This is useful in CI to catch configuration issues:
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"build": "hanzogui build --target web --expect-optimizations 10 ./src -- next build"
|
|
}
|
|
}
|
|
```
|
|
|
|
If the compiler produces fewer than the expected number of optimizations, the build will fail with an error message showing the actual count. This helps catch:
|
|
|
|
- Misconfigured `components` array
|
|
- Wrong source paths
|
|
- Configuration files not being found
|
|
|
|
#### Platform-Specific File Handling
|
|
|
|
The CLI automatically handles platform-specific files (`.web.tsx`,
|
|
`.native.tsx`, `.ios.tsx`, `.android.tsx`):
|
|
|
|
- Files with `.web.tsx` extensions are optimized for web only
|
|
- Files with `.native.tsx`, `.ios.tsx`, or `.android.tsx` extensions are
|
|
optimized for native only
|
|
- Base files (`.tsx`) without platform-specific versions are optimized for all
|
|
platforms
|
|
- If both `.web.tsx` and `.native.tsx` exist, the base `.tsx` file is skipped
|
|
|
|
#### Package.json Exports Support
|
|
|
|
The CLI supports `package.json` exports for path-specific imports. For example:
|
|
|
|
```json
|
|
{
|
|
"exports": {
|
|
".": "./src/index.tsx",
|
|
"./components/Button": "./src/Button.tsx"
|
|
}
|
|
}
|
|
```
|
|
|
|
Both import styles work:
|
|
|
|
```tsx
|
|
import { Button } from '@my/ui'
|
|
import { Button } from '@my/ui/components/Button'
|
|
```
|
|
|
|
#### Integration Examples
|
|
|
|
This works with **any build tool** - just run `hanzogui build` before your build
|
|
command. Here are some examples:
|
|
|
|
**Next.js with Turbopack** (Turbopack doesn't support plugins yet):
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"dev": "next dev --turbopack",
|
|
"build": "hanzogui build --target web ./src -- next build"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Vite, Remix, or any other bundler:**
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"build": "hanzogui build --target web ./src -- vite build"
|
|
}
|
|
}
|
|
```
|
|
|
|
**React Native / Expo:**
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"build:ios": "hanzogui build --target native ./src -- eas build --platform ios",
|
|
"build:android": "hanzogui build --target native ./src -- eas build --platform android"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Using --output (no file restoration needed):**
|
|
|
|
If you prefer to output optimized files to a separate directory instead of modifying
|
|
source files in-place, use `--output`:
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"build": "hanzogui build --target web --output ./dist ./src && next build"
|
|
}
|
|
}
|
|
```
|
|
|
|
With `--output`, your source files are never modified. The optimized files are
|
|
written to the output directory with their directory structure preserved.
|
|
|
|
**Using --output-around (platform-specific files):**
|
|
|
|
The `--output-around` flag creates optimized platform-specific files (`.web.tsx` or
|
|
`.native.tsx`) next to your source files instead of modifying them. Bundlers
|
|
automatically pick up these files via platform-specific resolution:
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"prebuild:native": "hanzogui build --target native --output-around ./src",
|
|
"prebuild:web": "hanzogui build --target web --output-around ./src"
|
|
}
|
|
}
|
|
```
|
|
|
|
This transforms `Button.tsx` → creates `Button.native.tsx` or `Button.web.tsx`
|
|
alongside it. Metro/Expo uses `.native.tsx` on native, and web bundlers use `.web.tsx`.
|
|
|
|
The Gui CLI optimizes your components in-place (or to an output directory),
|
|
then your bundler processes the already-optimized files.
|
|
|
|
**Learn more:** See the [CLI Guide](/docs/guides/cli) for documentation on all
|
|
CLI commands including `check`, `generate`, `add`, and more.
|
|
|
|
## Props
|
|
|
|
All compiler plugins accept the same options:
|
|
|
|
<PropsTable
|
|
data={[
|
|
{
|
|
name: 'config',
|
|
required: false,
|
|
type: 'string',
|
|
typeSimple: 'enum',
|
|
default: "'./hanzogui.config.ts'",
|
|
description:
|
|
'Relative path to your hanzogui.config.ts file which should export default the result from createGui. Can be set in hanzogui.build.ts instead.',
|
|
},
|
|
{
|
|
name: 'components',
|
|
required: false,
|
|
type: 'string[]',
|
|
typeSimple: 'enum',
|
|
default: "['hanzogui']",
|
|
description: `Array of npm modules containing Gui components which you'll be using in your app. For example: if you are using the base Gui components. This directs the compiler to load and optimize.`,
|
|
},
|
|
{
|
|
name: 'importsWhitelist',
|
|
required: false,
|
|
type: 'string[]',
|
|
typeSimple: 'enum',
|
|
description: `Array of whitelisted file paths (always end in .js) which the compiler may try and import and parse at build-time. It is normalized to ".js" ending for all file extensions (js, jsx, tsx, ts). This usually should be set to something like ['constants.js', 'colors.js'] for example, where you have a couple mostly static files of constants that are used as default values for styles.`,
|
|
},
|
|
{
|
|
name: 'logTimings',
|
|
required: false,
|
|
type: 'boolean',
|
|
typeSimple: 'enum',
|
|
default: 'true',
|
|
description:
|
|
'Gui outputs information for each file it compiles on how long it took to run, how many components it optimized, and how many it flattened. Set to false to disable these logs.',
|
|
},
|
|
{
|
|
name: 'disable',
|
|
required: false,
|
|
type: 'boolean',
|
|
typeSimple: 'enum',
|
|
default: 'false',
|
|
description: 'Disable everything - debug and extraction.',
|
|
},
|
|
{
|
|
name: 'disableExtraction',
|
|
required: false,
|
|
type: 'boolean',
|
|
typeSimple: 'enum',
|
|
default: 'false',
|
|
description:
|
|
'Disable extraction to CSS completely, instead fully relying on runtime. Setting this to true speed up development as generally your app will hot reload the Gui configuration itself.',
|
|
},
|
|
{
|
|
name: 'disableDebugAttr',
|
|
required: false,
|
|
type: 'boolean',
|
|
typeSimple: 'enum',
|
|
default: 'false',
|
|
description:
|
|
'If enabled along with disableExtraction, all parsing will turn off. Normally turning off disableExtraction will keep the helpful debug attributes in DOM',
|
|
},
|
|
{
|
|
name: 'disableFlattening',
|
|
required: false,
|
|
type: 'boolean',
|
|
typeSimple: 'enum',
|
|
default: 'false',
|
|
description: 'Turns off tree-flattening.',
|
|
},
|
|
{
|
|
name: 'enableDynamicEvaluation',
|
|
required: false,
|
|
type: 'boolean',
|
|
typeSimple: 'enum',
|
|
default: 'false',
|
|
description:
|
|
'(Experimental) Enables further extracting of any styled component, even if not in your components. See below for more information.',
|
|
},
|
|
{
|
|
name: 'outputCSS',
|
|
required: false,
|
|
type: 'string',
|
|
typeSimple: 'enum',
|
|
description:
|
|
'Path to output extracted CSS. When set, the compiler automatically sets GUI_DID_OUTPUT_CSS which tree-shakes all runtime CSS generation code from your bundle (~6KB gzipped savings).',
|
|
},
|
|
]}
|
|
/>
|
|
|
|
### Dynamic Evaluation
|
|
|
|
By default the Gui compiler only optimizes `styled` expressions found in the
|
|
modules defined by your `components` config. This means if you do an inline
|
|
`styled()` inside your actual app directory, it will default to runtime style
|
|
insertion.
|
|
|
|
This is typically Good Enough™️. As long as you define most of your common
|
|
components there, you'll get a very high hit rate of compiled styles being used
|
|
and runtime generation being skipped, as atomic styles with your design system
|
|
tokens will be mostly pre-generated.
|
|
|
|
Gui has _experimental_ support for loading any component, even if it occurs
|
|
somewhere outside your configured components modules. This is called "dynamic
|
|
loading", for now. You can enable it with the setting `enableDynamicEvaluation`
|
|
as seen above in the props table.
|
|
|
|
The way it works is, when the compiler detects a styled() expression outside one
|
|
of the defined component directories, it will run the following:
|
|
|
|
1. First, read the file and use a custom `babel` transform to _force_ all
|
|
top-level variables to be exported.
|
|
2. Then, run `esbuild` and bundle the entire file to a temporary file in the
|
|
same directory, something like `.hanzogui-dynamic-eval-ComponentName.js`
|
|
3. Now, read the file in and load all new definitions found.
|
|
4. Finally, continue with optimization, using the newly optimized component.
|
|
|
|
You may see why this is experimental. It's very convenient as a developer, but
|
|
has a variety of edge cases that can be confusing or breaking, and we want to
|
|
avoid installation woes. Though it does continue on error and work generally, it
|
|
outputs warnings in Webpack currently due to our plugin not properly indicating
|
|
to Webpack about the new files (a fixable bug), which causes big yellow warning
|
|
output and a cache de-opt.
|
|
|
|
We're leaving this feature under the environment variable while it matures. Let
|
|
us know if you find it useful.
|
|
|
|
### Disabling the compiler
|
|
|
|
You can disable the compiler optimizations for an entire file with a comment at
|
|
the top of your file:
|
|
|
|
```tsx
|
|
// hanzogui-ignore
|
|
```
|
|
|
|
You can disable the compiler optimization for a single component with the
|
|
boolean property `disableOptimization`:
|
|
|
|
```tsx
|
|
import { View } from '@hanzogui/core'
|
|
|
|
export default () => <View disableOptimization />
|
|
```
|
|
|
|
### Web-only apps
|
|
|
|
This only applies to web-only apps. Native and universal apps already have
|
|
`react-native` installed. For web-only apps, you still need `react-native`
|
|
types in the parent project or workspace root for prop typing and autocomplete.
|
|
You can either install `react-native` directly:
|
|
|
|
```bash
|
|
yarn add react-native
|
|
```
|
|
|
|
Or, if you only want the types:
|
|
|
|
```bash
|
|
yarn add [email protected] @types/react-native
|
|
```
|
|
|
|
Gui's web runtime doesn't require `react-native` at runtime, but current
|
|
prop typing and autocomplete rely on the `react-native` types being present.
|