Skip to content

Commit

Permalink
Donate next.js example from aspect-build/bazel-examples
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeagle committed Sep 26, 2023
1 parent 767dc26 commit 2b7c21c
Show file tree
Hide file tree
Showing 40 changed files with 5,914 additions and 0 deletions.
52 changes: 52 additions & 0 deletions .bazelci/frontend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#
# Bazel releases
#
lts: &lts
bazel: latest

rolling: &rolling
bazel: rolling

#
# Commmon features by platform
#
linux: &linux
platform: ubuntu1804

macos: &macos
platform: macos

#
# Test sets
#
basics: &basics
working_directory: ../frontend
build_targets:
- "..."
test_targets:
- "..."

#
# Finally, the cross product of tests X platforms X bazel release
#
tasks:
basics_linux_lts:
name: basics_linux_lts
<<: *linux
<<: *lts
<<: *basics
basics_linux_latest:
name: basics_linux_latest
<<: *linux
<<: *rolling
<<: *basics
basics_macos_lts:
name: basics_macos_lts
<<: *macos
<<: *lts
<<: *basics
basics_macos_latest:
name: basics_macos_latest
<<: *macos
<<: *rolling
<<: *basics
1 change: 1 addition & 0 deletions .bazelci/presubmit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ imports:
- android.yml
- configurations.yml
- misc.yml
- frontend.yml
- rules.yml
- tutorials.yml
- bzlmod.yml
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ bazel-*
target/
# Xcode
*.xcodeproj
node_modules
3 changes: 3 additions & 0 deletions frontend/.bazelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
next.js/node_modules
packages/one/node_modules
9 changes: 9 additions & 0 deletions frontend/.bazelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
common --enable_bzlmod

# passes an argument `--skipLibCheck` to *every* spawn of the TypeScript compiler, tsc
build --@aspect_rules_ts//ts:skipLibCheck=always
fetch --@aspect_rules_ts//ts:skipLibCheck=always
query --@aspect_rules_ts//ts:skipLibCheck=always

# Required for rules_js
build --enable_runfiles
1 change: 1 addition & 0 deletions frontend/.bazelversion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6.4.0rc1
6 changes: 6 additions & 0 deletions frontend/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Disabling pnpm [hoisting](https://pnpm.io/npmrc#hoist) by setting `hoist=false` is recommended on
# projects using rules_js so that pnpm outside of Bazel lays out a node_modules tree similar to what
# rules_js lays out under Bazel (without a hidden node_modules/.pnpm/node_modules). See
# https://github.com/aspect-build/rules_js/blob/7377f2d0387cc2a9251137929b1c53ccdb3fbcf0/docs/npm_import.md#npm_translate_lock
# documentation for more information.
hoist=false
7 changes: 7 additions & 0 deletions frontend/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"Root BUILD file for all frontend examples"

load("@npm//:defs.bzl", "npm_link_all_packages")

# Create the root of the "virtual store" of npm dependencies under bazel-out.
# This must be done in the package where the pnpm workspace is rooted.
npm_link_all_packages(name = "node_modules")
28 changes: 28 additions & 0 deletions frontend/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"Bazel module dependencies, see https://bazel.build/external/overview#bzlmod"

bazel_dep(name = "aspect_bazel_lib", version = "1.33.0")
bazel_dep(name = "aspect_rules_jest", version = "0.19.5")
bazel_dep(name = "aspect_rules_js", version = "1.32.2")
bazel_dep(name = "aspect_rules_ts", version = "2.0.0")
bazel_dep(name = "aspect_rules_rollup", version = "1.0.0")
bazel_dep(name = "bazel_skylib", version = "1.4.2")

npm = use_extension("@aspect_rules_js//npm:extensions.bzl", "npm", dev_dependency = True)
npm.npm_translate_lock(
name = "npm",
npmrc = "//:.npmrc",
pnpm_lock = "//:pnpm-lock.yaml",
public_hoist_packages = {
"@next/eslint-plugin-next": ["next.js"],
},
verify_node_modules_ignored = "//:.bazelignore",
)
use_repo(npm, "npm")

rules_ts_ext = use_extension(
"@aspect_rules_ts//ts:extensions.bzl",
"ext",
dev_dependency = True,
)
rules_ts_ext.deps()
use_repo(rules_ts_ext, "npm_typescript")
8 changes: 8 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Frontends with Bazel

This folder contains various examples for writing JavaScript applications with Bazel.

Bazel's [rules_js] uses the pnpm package manager. This folder is the root of a pnpm workspace.
This allows npm packages within this monorepo to depend on each other.

[rules_js]: https://docs.aspect.build/rules/aspect_rules_js
1 change: 1 addition & 0 deletions frontend/WORKSPACE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Marker that this folder is the root of a Bazel workspace
2 changes: 2 additions & 0 deletions frontend/next.js/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const base = require("eslint-config-next/core-web-vitals");
module.exports = base;
79 changes: 79 additions & 0 deletions frontend/next.js/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
load("@aspect_rules_js//js:defs.bzl", "js_library", "js_test")
load("@aspect_rules_ts//ts:defs.bzl", "ts_config")
load("@bazel_skylib//rules:build_test.bzl", "build_test")
load("@npm//:defs.bzl", "npm_link_all_packages")
load("@npm//next.js:next/package_json.bzl", next_bin = "bin")
load("//next.js:defs.bzl", "next")

npm_link_all_packages(name = "node_modules")

next_bin.next_binary(
name = "next_js_binary",
visibility = ["//visibility:public"],
)

ts_config(
name = "tsconfig",
src = "tsconfig.json",
visibility = ["//visibility:public"],
)

next(
name = "next",
srcs = [
"//next.js/pages",
"//next.js/public",
"//next.js/styles",
],
data = [
"next.config.js",
"package.json",
":node_modules/@bazel-example/one",
":node_modules/is-even",
":node_modules/next",
":node_modules/react",
":node_modules/react-dom",
":node_modules/typescript",
],
next_bin = "../../node_modules/.bin/next",
next_js_binary = ":next_js_binary",
)

# A smoke test on the artifact produced by the :next target
js_test(
name = "build_smoke_test",
chdir = package_name(),
data = [":next"],
entry_point = "build_smoke_test.js",
)

js_library(
name = "package_json",
srcs = ["package.json"],
visibility = ["//visibility:public"],
)

js_library(
name = "jest_config",
srcs = ["jest.config.js"],
visibility = ["//visibility:public"],
deps = [":node_modules/next"],
)

js_library(
name = "eslintrc",
srcs = [".eslintrc.js"],
visibility = ["//visibility:public"],
deps = [":node_modules/eslint-config-next"],
)

build_test(
name = "build_test",
targets = [
":next",
# Not build testing the `:next_export` target since this `next export` writes back to the `.next` directory which
# causes issues with trying to write to an input. See https://github.com/vercel/next.js/issues/43344.
# TODO: fix in Next.js (https://github.com/vercel/next.js/issues/43344) or find work-around.
# ":next_export",
],
)
91 changes: 91 additions & 0 deletions frontend/next.js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Next.js Bazel example

This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) and then Bazelified.

TypeScript transpilation and type checking has been broken out into fine-grained `ts_project` targets.
This is a small example with only two Typescript source directories so the performance benefit of using fine grained targets will be negligible here.

In a large application or monorepo, splitting Typescript transpilation & type checking across many targets can speed up the build with parallelization and caching. It also allows for massive parallelization with remote execution. Read
https://blog.aspect.dev/typescript-with-rbe for more information on using remote execution with Typescript.

## Usage

The `package.json` scripts have been updated to call Bazel instead of Next.js so these scripts can be
used as they would be in a typical Next.js configuration.
For example, run `npm run dev` to run the development server in watch mode (equivalent to running `next dev`).
Run `npm start` to run the production server in watch mode (equivalent to running `next start`).

### Setup

We recommend installing a `node_modules` folder in the source tree, like with any frontend project.
Just run `pnpm install`.
Bazel itself doesn't depend on that folder, but it is needed so your editor can find typings.

### Building

Run `pnpm run build`. This runs `bazel build //next.js:next`, the Bazel equivalent of running `next build`.
The output `.next` folder can be found under `bazel-bin/next.js/.next`.

### Exporting

Run `pnpm run export`. This runs `bazel build //next.js:next_export`, the Bazel equivalent of running `next export`.
The output `out` folder can be found under `bazel-bin/next.js/out`.

NB This target will fail on some systems or cause unnecessary rebuilds of the `.next` target due to `next export` writing
back to the `.next` input directory which is write-protected input under Bazel. See https://github.com/vercel/next.js/issues/43344.

TODO: Fix issue in Next.js (https://github.com/vercel/next.js/issues/43344) or find work-around.

### Styles

This example only has the .css styles generated by [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). These don't require any build steps. Showing how to create fine grained
targets to pre-process `.scss` and `.less` into `.css` would be useful in this example in the future.

TODO: add .scss and/or .less styles and pre-process targets

### Releasing and deploying

This examples doesn't directly cover releasing and deploying a Next.js application built with Bazel
but it should not diverge much from releasing and deploying a Next.js application built outside of Bazel
since the output of the Bazel build is the shape as the output you would get from running the vernacular
Next.js tooling, namely a `.next` folder with all of the output artifacts that application is
comprised of:

```
$ pnpm run build
> [email protected] build /Users/greg/aspect/rules/bazel-examples/next.js
> bazel build //:build
INFO: Analyzed target //:build (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:build up-to-date:
bazel-bin/.next
INFO: Elapsed time: 0.260s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
$ ls -la bazel-bin/.next
total 608
drwxr-xr-x 16 greg wheel 512 28 Sep 14:06 .
drwxr-xr-x 11 greg wheel 352 28 Sep 14:06 ..
-rw-r--r-- 1 greg wheel 21 28 Sep 14:06 BUILD_ID
-rw-r--r-- 1 greg wheel 1078 28 Sep 14:06 build-manifest.json
drwxr-xr-x 5 greg wheel 160 28 Sep 14:06 cache
-rw-r--r-- 1 greg wheel 93 28 Sep 14:06 export-marker.json
-rw-r--r-- 1 greg wheel 441 28 Sep 14:06 images-manifest.json
-rw-r--r-- 1 greg wheel 103383 28 Sep 14:06 next-server.js.nft.json
-rw-r--r-- 1 greg wheel 20 28 Sep 14:06 package.json
-rw-r--r-- 1 greg wheel 312 28 Sep 14:06 prerender-manifest.json
-rw-r--r-- 1 greg wheel 2 28 Sep 14:06 react-loadable-manifest.json
-rw-r--r-- 1 greg wheel 2598 28 Sep 14:06 required-server-files.json
-rw-r--r-- 1 greg wheel 335 28 Sep 14:06 routes-manifest.json
drwxr-xr-x 10 greg wheel 320 28 Sep 14:06 server
drwxr-xr-x 5 greg wheel 160 28 Sep 14:06 static
-rw-r--r-- 1 greg wheel 115279 28 Sep 14:06 trace
```

When built with Bazel, this folder doesn't end up as `.next` in your source tree
because Bazel doesn't write output files to the source tree. Instead the folder can be found
via the `bazel-bin` symlink create by Bazel as `bazel-bin/.next`. You release and deploy tooling
would use this folder after running the Bazel build.
7 changes: 7 additions & 0 deletions frontend/next.js/build_smoke_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const assert = require('assert');
const fs = require('fs');

// Make sure there's a file like .next/static/chunks/main-320707e1a775df4d.js
const files = fs.readdirSync('.next/static/chunks');
console.log(files);
assert.ok(files.some((f) => /main-[0-9a-f]{16}\.js/.test(f)));
Loading

0 comments on commit 2b7c21c

Please sign in to comment.