blob: e773709522c62faa724ad1e0f163dc6e3bfb09ac [file] [log] [blame]
<!DOCTYPE html>
<html itemscope itemtype="https://schema.org/WebPage" lang="en">
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1" name="viewport">
<link href="/rules_nodejs/" rel="canonical">
<link href="" rel="shortcut icon" type="image/png">
<title>rules_nodejs - Home</title>
<!-- Webfont -->
<link href="//fonts.googleapis.com/css?family=Source+Code+Pro:400,500,700|Open+Sans:400,600,700,800" rel="stylesheet">
<!-- Bootstrap -->
<link crossorigin="anonymous" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" rel="stylesheet">
<!-- Font Awesome -->
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<!-- Custom stylesheet -->
<link href="/rules_nodejs/css/main.css" rel="stylesheet">
<!-- metadata -->
<meta content="rules_nodejs" name="og:title"/>
<meta content="JavaScript and NodeJS rules for Bazel" name="og:description"/>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" id="common-nav">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button class="navbar-toggle collapsed" data-target="#bs-example-navbar-collapse-1" data-toggle="collapse"
type="button">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/rules_nodejs/">
<img class="navbar-logo" src="/rules_nodejs/images/bazel-navbar.svg">
</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<form class="navbar-form navbar-right" action="/rules_nodejs/search.html" id="cse-search-box">
<div class="form-group">
<input type="hidden" name="cx" value="2735dc72dd157bd19">
<input type="search" name="q" id="q" class="form-control input-sm" placeholder="Search">
</div>
</form>
<ul class="nav navbar-nav navbar-right">
<li><a href="https://github.com/bazelbuild/rules_nodejs">GitHub</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container vpad">
<div class="row">
<div class="col-md-2">
<a aria-controls="sidebar-nav"
aria-expanded="false" class="btn btn-default btn-lg btn-block sidebar-toggle" data-toggle="collapse"
href="#sidebar-nav">
<i class="glyphicon glyphicon-menu-hamburger"></i> Navigation
</a>
<nav class="sidebar collapse" id="sidebar-nav">
<select onchange="location.href=this.value">
<option selected disabled hidden>Version: 3.x</option>
<option value="/rules_nodejs/">3.x</option>
<option value="https://docs.aspect.dev/rules_nodejs/">2.3</option>
</select>
<h3>rules_nodejs</h3>
<ul class="sidebar-nav">
<li><a href="/rules_nodejs/">Introduction</a></li>
<li><a href="install.html">Installation</a></li>
<li><a href="repositories.html">Repositories</a></li>
<li><a href="dependencies.html">Dependencies</a></li>
<li><a href="debugging.html">Debugging</a></li>
<li><a href="stamping.html">Stamping</a></li>
<li><a href="changing-rules.html">Making changes to rules_nodejs</a></li>
<li><a href="examples.html">Examples</a></li>
</ul>
<h3>Rules</h3>
<ul class="sidebar-nav">
<li><a href="/rules_nodejs/Built-ins.html">Built-ins</a></li>
<li><a href="/rules_nodejs/Concatjs.html">Concatjs</a></li>
<li><a href="/rules_nodejs/Cypress.html">Cypress</a></li>
<li><a href="/rules_nodejs/Jasmine.html">Jasmine</a></li>
<li><a href="/rules_nodejs/Labs.html">Labs</a></li>
<li><a href="/rules_nodejs/Protractor.html">Protractor</a></li>
<li><a href="/rules_nodejs/Providers.html">Providers</a></li>
<li><a href="/rules_nodejs/Rollup.html">Rollup</a></li>
<li><a href="/rules_nodejs/Terser.html">Terser</a></li>
<li><a href="/rules_nodejs/TypeScript.html">TypeScript</a></li>
<li><a href="/rules_nodejs/esbuild.html">esbuild</a></li>
</ul>
<h3>Community</h3>
<ul class="sidebar-nav">
<li><a href="https://github.com/bazelbuild/rules_nodejs/blob/master/CONTRIBUTING.md">Contribute to
rules_nodejs</a></li>
<li><a href="https://slack.bazel.build">Join #javascript on Slack</a></li>
<li><a href="https://github.com/bazelbuild/rules_nodejs/issues">Issue Tracker</a></li>
<li><a href="https://github.com/bazelbuild/rules_nodejs">Github</a></li>
</ul>
</nav>
</div>
<div class="col-md-8">
<div class="content">
<h1 id="bazel-javascript-rules">Bazel JavaScript rules</h1>
<p>Bazel is Google’s build system.
It powers development at large scale by caching intermediate build artifacts,
allowing build and test to be incremental and massively parallelizable.
Read more at <a href="https://bazel.build">https://bazel.build</a></p>
<p>This JavaScript support lets you build and test code that targets a JavaScript runtime, including NodeJS and browsers.</p>
<h2 id="scope-of-the-project">Scope of the project</h2>
<p>This repository contains three layers:</p>
<ol>
<li>A toolchain that fetches a hermetic node, npm, and yarn (independent of what’s on the developer’s machine), installs dependencies using one of these tools, and generates <code class="language-plaintext highlighter-rouge">BUILD</code> files so that Bazel can refer to those dependencies</li>
<li>“Built-in” rules distributed by a <code class="language-plaintext highlighter-rouge">.tgz</code> archive, which give the basic ability to run Node.js programs</li>
<li>Custom rules that are distributed on <a href="http://npmjs.com/~bazel">npm</a> that more tightly integrate particular JS tooling options with Bazel</li>
</ol>
<p>If you would like to request a new rule, please open a <a href="https://github.com/bazelbuild/rules_nodejs/issues/new">feature request</a>, describe your use case, why it’s important, and why you can’t do it within the existing rules. Then the maintainers can decide if it is within the scope of the project and will have a large enough impact to warrant the time required to implement.</p>
<p>If you would like to write a rule outside the scope of the projects we recommend hosting them in your GitHub account or the one of your organization.</p>
<h2 id="design">Design</h2>
<p>Our goal is to make Bazel be a minimal layering on top of existing npm tooling, and to have maximal compatibility with those tools.</p>
<p>This means you won’t find a “Webpack vs. Rollup” debate here. You can run whatever tools you like under Bazel. In fact, we recommend running the same tools you’re currently using, so that your Bazel migration only changes one thing at a time.</p>
<p>In many cases, there are trade-offs. We try not to make these decisions for you, so instead of paving one “best” way to do things like many JS tooling options, we provide multiple ways. This increases complexity in understanding and using these rules, but also avoids choosing a wrong “winner”. For example, you could install the dependencies yourself, or have Bazel manage its own copy of the dependencies, or have Bazel symlink to the ones in the project.</p>
<p>The JS ecosystem is also full of false equivalence arguments. The first question we often get is “What’s better, Webpack or Bazel?”.
This is understandable, since most JS tooling is forced to provide a single turn-key experience with an isolated ecosystem of plugins, and humans love a head-to-head competition.
Instead Bazel just orchestrates calling these tools.</p>
<h2 id="quickstart">Quickstart</h2>
<p>First we create a new workspace, which will be in a new directory.
We can use the <code class="language-plaintext highlighter-rouge">@bazel/create</code> npm package to do this in one command.
This is the fastest way to get started for most use cases.</p>
<blockquote>
<p>See <a href="install.html">the installation page</a> for details and alternative methods.</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>npm init @bazel my_workspace
<span class="nv">$ </span><span class="nb">cd </span>my_workspace
</code></pre></div></div>
<blockquote>
<p>You could do the same thing with yarn:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>yarn create @bazel my_workspace
<span class="nv">$ </span><span class="nb">cd </span>my_workspace
</code></pre></div> </div>
<p>Both of these commands are equivalent to <code class="language-plaintext highlighter-rouge">npx @bazel/create</code> which downloads the latest version of the <code class="language-plaintext highlighter-rouge">@bazel/create</code> package from npm and runs the program it contains.
Run the tool with no arguments for command-line options and next steps.</p>
</blockquote>
<p>Next we install some development tools.
For this example, we’ll use Babel to transpile our JavaScript, Mocha for running tests, and http-server to serve our app.
These are arbitrary choices, you may use whatever are your favorites.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>npm <span class="nb">install</span> @babel/core @babel/cli @babel/preset-env http-server mocha domino
</code></pre></div></div>
<p>Let’s run these tools with Bazel. There are two ways to run tools:</p>
<ul>
<li>Use an auto-generated Bazel rule by importing from an <code class="language-plaintext highlighter-rouge">index.bzl</code> file in the npm package</li>
<li>Use a custom rule in rules_nodejs or write one yourself</li>
</ul>
<p>In this example we use the auto-generated rules.
First we need to import them, using a load statement.
So edit <code class="language-plaintext highlighter-rouge">BUILD.bazel</code> and add:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">load</span><span class="p">(</span><span class="s">"@npm//@babel/cli:index.bzl"</span><span class="p">,</span> <span class="s">"babel"</span><span class="p">)</span>
<span class="n">load</span><span class="p">(</span><span class="s">"@npm//mocha:index.bzl"</span><span class="p">,</span> <span class="s">"mocha_test"</span><span class="p">)</span>
<span class="n">load</span><span class="p">(</span><span class="s">"@npm//http-server:index.bzl"</span><span class="p">,</span> <span class="s">"http_server"</span><span class="p">)</span>
</code></pre></div></div>
<blockquote>
<p>This shows us that rules_nodejs has told Bazel that a workspace named @npm is available
(think of the at-sign like a scoped package for Bazel).
rules_nodejs will add <code class="language-plaintext highlighter-rouge">index.bzl</code> files exposing all the binaries the package manager installed
(the same as the content of the <code class="language-plaintext highlighter-rouge">node_modules/.bin folder</code>).
The three tools we installed are in this @npm scope and each has an index file with a .bzl extension.</p>
</blockquote>
<p>Next we teach Bazel how to transform our JavaScript inputs into transpiled outputs.
Here we assume that you have <code class="language-plaintext highlighter-rouge">app.js</code> and <code class="language-plaintext highlighter-rouge">es5.babelrc</code> in your project. See <a href="https://github.com/bazelbuild/rules_nodejs/tree/1.4.0/examples/webapp">our example webapp</a> for an example of what those files might look like.
Now we want Babel to produce <code class="language-plaintext highlighter-rouge">app.es5.js</code> so we add to <code class="language-plaintext highlighter-rouge">BUILD.bazel</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">babel</span><span class="p">(</span>
<span class="n">name</span> <span class="o">=</span> <span class="s">"compile"</span><span class="p">,</span>
<span class="n">data</span> <span class="o">=</span> <span class="p">[</span>
<span class="s">"app.js"</span><span class="p">,</span>
<span class="s">"es5.babelrc"</span><span class="p">,</span>
<span class="s">"@npm//@babel/preset-env"</span><span class="p">,</span>
<span class="p">],</span>
<span class="n">outs</span> <span class="o">=</span> <span class="p">[</span><span class="s">"app.es5.js"</span><span class="p">],</span>
<span class="n">args</span> <span class="o">=</span> <span class="p">[</span>
<span class="s">"app.js"</span><span class="p">,</span>
<span class="s">"--config-file"</span><span class="p">,</span>
<span class="s">"./$(execpath es5.babelrc)"</span><span class="p">,</span>
<span class="s">"--out-file"</span><span class="p">,</span>
<span class="s">"$(execpath app.es5.js)"</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>
<blockquote>
<p>This just calls the Babel CLI, so you can see <a href="https://babeljs.io/docs/en/babel-cli">their documentation</a> for what arguments to pass.
We use the $(execpath) helper in Bazel so we don’t need to hardcode paths to the inputs or outputs.</p>
</blockquote>
<p>We can now build the application: <code class="language-plaintext highlighter-rouge">npm run build</code></p>
<p>and we see the .js outputs from babel appear in the <code class="language-plaintext highlighter-rouge">dist/bin</code> folder.</p>
<p>Let’s serve the app to see how it looks, by adding to <code class="language-plaintext highlighter-rouge">BUILD.bazel</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http_server(
name = "server",
data = [
"index.html",
"app.es5.js",
],
args = ["."],
)
</code></pre></div></div>
<p>Add a <code class="language-plaintext highlighter-rouge">serve</code> entry to the scripts in <code class="language-plaintext highlighter-rouge">package.json</code>:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"serve"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ibazel run :server"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<blockquote>
<p>ibazel is the watch mode for bazel.</p>
<p>Note that on Windows, you need to pass <code class="language-plaintext highlighter-rouge">--enable_runfiles</code> flag to Bazel.
That’s because Bazel creates a directory where inputs and outputs both conveniently appear together.</p>
</blockquote>
<p>Now we can serve the app: <code class="language-plaintext highlighter-rouge">npm run serve</code></p>
<p>Finally we’ll add a test using Mocha, and the domino package so we don’t need a browser. Add to <code class="language-plaintext highlighter-rouge">BUILD.bazel</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">mocha_test</span><span class="p">(</span>
<span class="n">name</span> <span class="o">=</span> <span class="s">"unit_tests"</span><span class="p">,</span>
<span class="n">args</span> <span class="o">=</span> <span class="p">[</span><span class="s">"*.spec.js"</span><span class="p">],</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">glob</span><span class="p">([</span><span class="s">"*.spec.js"</span><span class="p">])</span> <span class="o">+</span> <span class="p">[</span>
<span class="s">"@npm//domino"</span><span class="p">,</span>
<span class="s">"app.es5.js"</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>
<p>Run the tests: <code class="language-plaintext highlighter-rouge">npm test</code></p>
<h2 id="next-steps">Next steps</h2>
<p>Look through the <code class="language-plaintext highlighter-rouge">/examples</code> directory in this repo for many examples of running tools under Bazel.</p>
<p>You might want to look through the API docs for custom rules such as TypeScript, Rollup, and Terser which add support beyond what you get from calling the CLI of those tools.</p>
</div>
</div>
<div class="col-md-2 sticky-sidebar">
<div class="right-sidebar">
<ul class="gh-links">
<li>
<i class="fa fa-github"></i>
<a href="https://github.com/bazelbuild/rules_nodejs/issues/new?title=Documentation issue: Home&labels=question/docs">Create
issue</a>
</li>
<li>
<i class="fa fa-pencil"></i>
<a class="gh-edit" href="https://github.com/bazelbuild/rules_nodejs/tree/stable/docs/index.md">Edit
this page</a>
</li>
</ul>
<ul class="section-nav">
<li class="toc-entry toc-h2"><a href="#scope-of-the-project">Scope of the project</a></li>
<li class="toc-entry toc-h2"><a href="#design">Design</a></li>
<li class="toc-entry toc-h2"><a href="#quickstart">Quickstart</a></li>
<li class="toc-entry toc-h2"><a href="#next-steps">Next steps</a></li>
</ul>
</div>
</div>
</div>
</div>
<footer class="footer">
<div class="container">
<div class="row">
<div class="col-lg-8">
<p class="text-muted">&copy; 2021 The rules_nodejs authors</p>
</div>
</div>
</div>
</footer>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script crossorigin="anonymous"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<!-- Anchor JS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/anchor-js/3.2.0/anchor.min.js" type="text/javascript"></script>
<script>
// Automatically add anchors and links to all header elements that don't already have them.
anchors.options = { placement: 'left' };
anchors.add();
</script>
<script>
var shiftWindow = function () {
if (location.hash.length !== 0) {
window.scrollBy(0, -50);
}
};
window.addEventListener("hashchange", shiftWindow);
var highlightCurrentSidebarNav = function () {
var href = location.pathname;
var item = $('#sidebar-nav [href$="' + href + '"]');
if (item) {
var li = item.parent();
li.addClass("active");
if (li.parent() && li.parent().is("ul")) {
do {
var ul = li.parent();
if (ul.hasClass("collapse")) {
ul.collapse("show");
}
li = ul.parent();
} while (li && li.is("li"));
}
}
};
$(document).ready(function () {
// Scroll to anchor of location hash, adjusted for fixed navbar.
window.setTimeout(function () {
shiftWindow();
}, 1);
// Flip the caret when submenu toggles are clicked.
$(".sidebar-submenu").on("show.bs.collapse", function () {
var toggle = $('[href$="#' + $(this).attr('id') + '"]');
if (toggle) {
toggle.addClass("dropup");
}
});
$(".sidebar-submenu").on("hide.bs.collapse", function () {
var toggle = $('[href$="#' + $(this).attr('id') + '"]');
if (toggle) {
toggle.removeClass("dropup");
}
});
// Highlight the current page on the sidebar nav.
highlightCurrentSidebarNav();
});
</script>
</body>
</html>