Skip to content

Latest commit

History

62 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

BazelNodeJsYarn

rules_nodeBuild Status

Rules

RuleDescription
node_repositoriesInstall node toolchain.
yarn_modulesInstall a set node module dependencies using yarn.
node_moduleDefine a node module from a set of source files (having an optional main (or index) entry point).
node_binaryRun a node module.
node_testRun a node binary as a bazel test.
mocha_testRun a mocha test script.

node_repositories

WORKSPACE rule that downloads and configures node based on your operating system. Includes node (8.15.1) and yarn (1.0.1).

RULES_NODE_COMMIT='...'# Update to current HEADRULES_NODE_SHA256='...'http_archive(
name="org_pubref_rules_node",
url="https://github.com/pubref/rules_node/archive/%s.zip"%RULES_NODE_COMMIT,
strip_prefix="rules_node-%s"%RULES_NODE_COMMIT,
sha256=RULES_NODE_SHA256,
)
load("@org_pubref_rules_node//node:rules.bzl", "node_repositories")
node_repositories()

yarn_modules

Install a set of module dependencies into a yarn_modules folder as an external workspace. Requires either a package.json file or deps as input.

# In WORKSPACEload("@org_pubref_rules_node//node:rules.bzl", "yarn_modules")
# Use a package.json file as input. Location of the package json# is arbitrary.yarn_modules(
name="yarn_modules",
package_json="//:package.json",
)
# Shortcut form without a separate package.json fileyarn_modules(
name="yarn_modules",
deps= {
"react": "15.3.2",
"react-dom": "15.3.2",
},
)

How It Works:

  1. Create an external workspace @yarn_modules at $(bazel info output_base)/external/yarn_modules.

  2. Invoke yarn install to create a node_modules folder and populate it with the necessary dependencies.

  3. Read the generated yarn.lock file, parse it, and write out a @yarn_modules//:BUILD file. This file contains a node_module rule foreach entry in the yarn.lock file, a node_module rule with the special name _all_, and a node_binary rule foreach executable script in the node_modules/.bin folder.

Note 1: You can inspect all the targets by running bazel query @yarn_modules//:*.

Note 2: The workspace name yarn_modules is arbitrary, choose whatever you like (other thannode_modules itself, that one doesn't work).

At this point you can use these rule targets as deps for your node_module rules. Example:

node_module(
name="my_module",
package_json="package.json",
srcs=glob(["**/*.js"]),
deps= [
"@yarn_modules//:_all_",
],
)

yarn_module attributes

TypeNameDescription
optionallabelpackage_jsonA package.json file containing the dependencies that should be installed.
optionalstring_dictdepsA mapping of name --> version for the dependencies that should be installed.
optionalstring_listpost_installA list of command-line arguments that should be invoked after the yarn install step. See #55.

Either package_json or deps must be present, but not both.

node_module

BUILD file rule that creates a folder which conforms to the nodejs Folders as Modules packaging structure. Example:

node_module(
name="my_module",
main="index.js",
srcs= [
"lib/util.js",
"lib/math.js",
],
version="1.2.0",
description="Example node module",
deps= [
"@yarn_modules//:lodash",
"@yarn_modules//:fs-extra",
],

When used in a node_binary rule, this ultimately materializes to:

node_modules/my_module
node_modules/my_module/package.json
node_modules/my_module/index.js
node_modules/my_module/lib/util.js
node_modules/my_module/lib/math.js
node_modules/lodash
node_modules/fs-extra

When used by other node_module rules, you can import the module as:

constmyModule=require("my_module");

There are three basic ways to create a node_module rule:

1. Creating a node_module with a package.json file

node_module(
name="my_module_1",
package_json="package.json", # label to the 'package.json' file to use directly
)

In this scenario, assumes the package.json file has an entry that specifies the main entrypoint (or not, if you follow the Files as Modules pattern).

2. Creating a node_module with a label to the main entrypoint source file

node_module(
name="my_module_2",
main="app.js", # label to the entrypoint file for the moduleversion="1.0.0", # optional arguments to populate the generated package.json file
...
)

In this scenario, a package.json file will be generated for the module that specifies the file you provide to the main attribute.

3. Creating a node_module with a label to the index.js entrypoint source file

node_module(
name="my_module_3",
index="index.js", # label to the 'index.js' file to use as the index
)

In this scenario, no package.json file is generated.

Module dependencies

Build up a dependency tree via the deps attribute:

node_module(
name = "my_module_3",
...
deps = [
"@yarn_modules//:_all_", # special token '_all_' to have access to all modules
":my_module_1",
],
)

Core node_module attributes

TypeNameDefaultDescription
optionallabelpackage_jsonNoneExplicitly name a package.json file to use for the module.
optionallabelmainNoneSource file named in the generated package.json main property.
optionallabelindexNoneSource file to be used as the index file (supresses generation of a package.json file).
optionallabel_listsrcs[]Source files to be included in the module.
optionallabel_listdeps[]node_module rule dependencies.

node_module attributes that affect the name of the module

For reference, by default a node_module rule //src/js:my_module generates node_modules/src/js/my_module.

TypeNameDefaultDescription
optionalstringnamespaceNoneSee 1
optionalstringmodule_name${ctx.label.package}/{ctx.label.name}See 2
optionalstringseparator/See 3

1 Use to scope the module with some organization prefix. Example: namespace = '@foo' generates node_modules/@foo/src/js/my_module.

2 Override the module name. Example: name = 'barbaz' with namespace (above) generates node_modules/@foo/barbaz

3Example: separator = '-' generates node_modules/src-js-my_module.

node_module attributes that affect the generated package.json

These are only relevant if you don't explicitly name a package.json file.

TypeNameDefaultDescription
optionalstringversion1.0.0Version string
optionalstringurlNoneUrl where the module tgz archive was resolved
optionalstringsha1NoneSha1 hash of of the resolved tgz archive
optionalstringdescriptionNoneModule description
optionalstring_dictexecutablesNoneA mapping from binary name to internal node module path. Example executables = { 'foo': 'bin/foo' }.

node_module attributes that affect the relative path of files included in the module

TypeNameDefaultDescription
optionalstringlayoutrelativeChanges the way files are included in the module. One of relative or workspace.

Consider a file with the label //src/js/my_module/app.js. With layout = 'relative' (the default), the location of the file becomes node_modules/src/js/my_module/app.js (skylark: file.short_path relative to module_name). Under layout = 'workspace', the it becomes node_modules/src/js/my_module/src/js/my_module/app.js (skylark: file.path). This is relevant only for protocol buffers where the generated sources import their own dependencies relative to the workspace, which needs to be preserved in the generated module.

node_binary

The node_binary rule writes a script to execute a module entrypoint.

load("@org_pubref_rules_node//node:rules.bzl", "node_binary")
node_binary(
name="foo",
entrypoint=":my_module_1",
)

In example above, we're specifying the name of a node_module to use as the entrypoint.

node_binary(
name="foo",
main="foo.js",
deps= [
":my_module_1
],
)

In this second example, we're specifying the name of a file to use as the entrypoint (under the hood, it will just build a node_module (called foo_module) for your single main foo.js file entrypoint, becoming equivalent to the first example).

node_binary(
name="foo",
entrypoint=":my_module_2",
executable="baz",
)

In this third example (above), we're specifying the name of the node module to start with (my_module_2) and the name of the executable within my_module_2 to run (baz). In this case the node_module rule definition for my_module_2 must have a string_dict with an entry for baz (like executables = { 'baz': 'bin/baz' }.

Output structure of files generated for a node_binary rule

A node_binary rule named foo will create a folder having exactly two entries:

  1. An executable shell script named foo.
  2. A folder which bundles up all the needed files in foo_files/.

Within foo_files/, there will also be exactly two entries:

  1. The node executable itself.
  2. The node_modules/ folder with all the built/copied modules (including the entrypoint module).

Building a deployable bundle

To generate a tarred/gzipped archive of the above example that you can ship as a single 'executable' self-contained package, invoke $ bazel build :{target}_deploy.tar.gz. This is similar in intent to the java {target}_deploy.jar implicit build rule.

$ bazel build :foo_deploy
Target //:foo_deploy.tar.gz up-to-date:
bazel-bin/foo_bundle.tgz
$ du -h bazel-bin/foo_bundle.tgz
33M bazel-bin/foo_bundle.tgz

node_test

The node_test rule is identical to node_binary, but sets the test = True flag such that it can be used as a bazel test.

mocha_test

Runs a mocha test identified by the start script given in main or module given in entrypoint.

Note: The mocha_test rule depends on @mocha_modules//:_all_, so you'll need to add this dependency in your WORKSPACE file:

yarn_modules(
name="mocha_modules",
deps= {
"mocha": "3.5.3",
}
)
mocha_test(
name="test",
main="test.js",
)

Conclusion

That's it! Please refer to the various workspaces in tests/ and the source for more detail.

About

Bazel build rules for node_js.

Topics

Resources

Stars

44 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages