<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Lewis Hemens</title>
    <description>The latest articles on Forem by Lewis Hemens (@lewish).</description>
    <link>https://forem.com/lewish</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F184857%2F43a0c13c-6826-4c4c-93d5-46755c356633.jpeg</url>
      <title>Forem: Lewis Hemens</title>
      <link>https://forem.com/lewish</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/lewish"/>
    <language>en</language>
    <item>
      <title>Testing data quality with SQL assertions</title>
      <dc:creator>Lewis Hemens</dc:creator>
      <pubDate>Wed, 26 Jun 2019 10:51:09 +0000</pubDate>
      <link>https://forem.com/dataform/testing-data-quality-with-sql-assertions-248g</link>
      <guid>https://forem.com/dataform/testing-data-quality-with-sql-assertions-248g</guid>
      <description>&lt;p&gt;Ensuring that data consumers can use their data to reliably answer questions is of paramount importance to any data analytics team. Having a mechanism to enforce high data quality across datasets is therefore a key requirement for these teams.&lt;/p&gt;

&lt;p&gt;Often, &lt;strong&gt;input data sources are missing rows, contain duplicates, or include just plain invalid data&lt;/strong&gt;. Over time, changes to business definitions or the underlying software which produces input data can cause drift in the meaning of columns - or even the overall structure of tables. Addressing these issues is critical to creating a successful data team and generating valuable, correct insights.&lt;/p&gt;

&lt;p&gt;In this article we explain the concept of a SQL data assertion, look at some common data quality problems, how to detect them, and - most importantly - &lt;strong&gt;how to fix them in a way that persists for all data consumers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The SQL snippets in this post apply to Google BigQuery but can be ported easily enough to Redshift, Postgres or Snowflake data warehouses.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a data assertion?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A data assertion is a query that looks for problems in a dataset&lt;/strong&gt;. If the query returns any rows then the assertion fails.&lt;/p&gt;

&lt;center&gt;
  &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.dataform.co%2Fimages%2Fblog%2Fassertions_illustration.png"&gt;
&lt;/center&gt;

&lt;p&gt;Data assertions are defined this way because it’s much easier to look for problems rather than the absence of them. It also means that assertion queries can themselves be used to quickly inspect the data causing the assertion to fail - making it easy to diagnose and fix the problem.&lt;/p&gt;

&lt;h4&gt;
  
  
  Checking field values
&lt;/h4&gt;

&lt;p&gt;Let’s take a look at a simple example.&lt;/p&gt;

&lt;p&gt;Assume that there is a &lt;code&gt;database.customers&lt;/code&gt; table containing information about customers in the database.&lt;br&gt;
Some checks that we might want to verify on the table’s contents include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The field &lt;code&gt;email_address&lt;/code&gt; is always set&lt;/li&gt;
&lt;li&gt;The field &lt;code&gt;customer_type&lt;/code&gt; is one of &lt;code&gt;“business”&lt;/code&gt; or &lt;code&gt;“individual”&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following simple query will return any rows violating these rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;email_address&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;customer_type&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;business&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;individual&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Checking for unique fields
&lt;/h4&gt;

&lt;p&gt;We may also want to run checks across more than one row. For example, we might want to verify that the &lt;code&gt;customer_id&lt;/code&gt; field is unique. A query like the following will return any duplicate &lt;code&gt;customer_id&lt;/code&gt; values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Combinining multiple assertions into a single query
&lt;/h4&gt;

&lt;p&gt;We can combine all of the above into a single query to quickly find any &lt;code&gt;customer_id&lt;/code&gt; value violating one of our rules using &lt;code&gt;UNION ALL&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;missing_email&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email_address&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;

&lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;invalid_customer_type&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;customer_type&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;business&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;individual&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;

&lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;duplicate_id&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have one query we can run to detect any problems in our table, and we can easily add another unioned &lt;code&gt;SELECT&lt;/code&gt; statement if we want to add new conditions in the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating clean datasets
&lt;/h3&gt;

&lt;p&gt;Now that we’ve detected the issues in our data, we need to clean them up. Ultimately choosing how to handle data quality issues depends on your business use case.&lt;/p&gt;

&lt;p&gt;In this example we will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove any rows that are missing the &lt;code&gt;email_address&lt;/code&gt; field&lt;/li&gt;
&lt;li&gt;Set a default customer type if it’s invalid&lt;/li&gt;
&lt;li&gt;Remove rows with duplicate &lt;code&gt;customer_id&lt;/code&gt; fields, retaining one row per &lt;code&gt;customer_id&lt;/code&gt; value (we don’t care which one)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Rather than editing the dataset directly, we can create a new clean copy of the dataset&lt;/strong&gt; - this gives us freedom to change or add rules in the future and avoids deleting any data.&lt;/p&gt;

&lt;p&gt;The following SQL query defines a view of our &lt;code&gt;database.customers&lt;/code&gt; table in which invalid rows are removed, default customer types are set, and duplicate rows for the same &lt;code&gt;customer_id&lt;/code&gt; are removed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ANY_VALUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;email_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ANY_VALUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="n"&gt;customer_type&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;individual&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;individual&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;business&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;   &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;business&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;
        &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="k"&gt;unknown&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;customer_type&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;email_address&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query can be used to create either a view or a table in our cloud data warehouse, perhaps called &lt;code&gt;database_clean.customers&lt;/code&gt;, which can be consumed in dashboards or by analysts who want to query the data.&lt;/p&gt;

&lt;p&gt;Now we've fixed the problem, we can check that the above query has correctly fixed the problems by re-running the original assertion on the new dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuous data quality testing
&lt;/h3&gt;

&lt;p&gt;Assertions should be run as part of any data pipelines to make sure breaking changes are picked up the moment they happen.&lt;/p&gt;

&lt;p&gt;If an assertion returns any rows, future steps in a pipeline should either fail, or a notification delivered to the data owner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dataform.co/developers?utm_medium=organic&amp;amp;utm_source=dev_to&amp;amp;utm_campaign=sql_assertions" rel="noopener noreferrer"&gt;Dataform&lt;/a&gt; has built in support for data assertions, and provides a way to run them as part of a larger SQL pipeline.&lt;/p&gt;

&lt;p&gt;These can be run at any frequency, and if an assertion fails an email will be sent to notify you of the problem. Dataform also provides a way to easily create new datasets in your warehouse, making managing the process of cleaning and testing your data extremely straightforward.&lt;/p&gt;

&lt;center&gt;
  &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.dataform.co%2Fimages%2Fdataform_sdk_schema.svg"&gt;
&lt;/center&gt;

&lt;p&gt;For more information on how to start writing data assertions with Dataform, check out the &lt;a href="https://docs.dataform.co/guides/assertions?utm_medium=organic&amp;amp;utm_source=dev_to&amp;amp;utm_campaign=sql_assertions" rel="noopener noreferrer"&gt;assertions documentation&lt;/a&gt; guide for Dataform’s &lt;a href="https://docs.dataform.co/" rel="noopener noreferrer"&gt;open-source framework&lt;/a&gt;, or &lt;a href="https://dataform.co/signup?utm_medium=organic&amp;amp;utm_source=dev_to&amp;amp;utm_campaign=sql_assertions" rel="noopener noreferrer"&gt;create an account for free&lt;/a&gt; and start using Dataform's fully managed Web platform.&lt;/p&gt;

</description>
      <category>sql</category>
    </item>
    <item>
      <title>Building a TypeScript monorepo with Bazel</title>
      <dc:creator>Lewis Hemens</dc:creator>
      <pubDate>Wed, 26 Jun 2019 08:24:16 +0000</pubDate>
      <link>https://forem.com/lewish/building-a-typescript-monorepo-with-bazel-4o7n</link>
      <guid>https://forem.com/lewish/building-a-typescript-monorepo-with-bazel-4o7n</guid>
      <description>&lt;p&gt;At &lt;a href="https://dataform.co/?utm_medium=organic&amp;amp;utm_source=dev_to&amp;amp;utm_campaign=bazel_ts_monorepo"&gt;Dataform&lt;/a&gt; we maintain a handful of NPM packages and a documentation site in one single monorepo and we do it all with &lt;a href="https://bazel.build"&gt;Bazel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm going to quickly talk through monorepos and Bazel, then deep dive into interesting parts our monorepo with some real code examples, covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bazel TypeScript basics&lt;/li&gt;
&lt;li&gt;Managing multiple packages in a single monorepo&lt;/li&gt;
&lt;li&gt;Building and publishishing NPM packages with Bazel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our project is open-source, so you can view all the code, or clone and build it with Bazel at: &lt;a href="https://github.com/dataform-co/dataform"&gt;https://github.com/dataform-co/dataform&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  All your code in one repo
&lt;/h2&gt;

&lt;p&gt;a.k.a the monorepo, &lt;em&gt;so hot right now&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I used to work at Google so I may be biased, but there is a huge amount of value to having all your code in one single repository. You end up spending a lot less time doing repetitive tasks, updating git submodules, pushing new packages, running bash scripts - the kind of things that distract you from the important task at hand.&lt;/p&gt;

&lt;p&gt;With a single code base, it becomes very easy to re-use code and libraries between different projects, but &lt;strong&gt;you need a good build system to make it work&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bazel
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;_{Fast, Correct} - Choose two_&lt;/code&gt; - &lt;a href="https://bazel.build"&gt;https://bazel.build&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ever tried to clone and compile an open-source repo just to spend 30 minutes wrangling with missing or broken system dependencies, mismatched versions and a myriad of bash scripts that just don't work? Yeah...&lt;/p&gt;

&lt;p&gt;Bazel is a build system. It's highly opinionated and tricky to master, but leaves you with an extremely fast, hermetic, and reproducible build process once adopted.&lt;/p&gt;

&lt;p&gt;Bazel is still fairly young, but the ecosystem is evolving extremely quickly. It's also built on solid foundations - being used internally at Google Bazel is called Blaze and helps to power Google's one colossal monorepo (literally all the code).&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;We maintain several NPM packages with inter dependencies. Our goals here are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To manage all packages in a single repository&lt;/li&gt;
&lt;li&gt;For our builds to be fast and reliable&lt;/li&gt;
&lt;li&gt;To test changes to multiple packages at the same time&lt;/li&gt;
&lt;li&gt;An easy way to manage versions across all these packages&lt;/li&gt;
&lt;li&gt;To write as few bash scripts as possible&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A basic Bazel TS rule
&lt;/h2&gt;

&lt;p&gt;Here's an example of how you build TypeScript library with Bazel. Our simplest package in the repo is &lt;a href="https://github.com/dataform-co/dataform/tree/master/core"&gt;&lt;code&gt;@dataform/core&lt;/code&gt;&lt;/a&gt; and we'll use this as an example for most of the post.&lt;/p&gt;

&lt;p&gt;The folder looks like a normal TS package, except for the &lt;code&gt;BUILD&lt;/code&gt; file. Here's the part of that file that actually compiles the TypeScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ts_library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"core"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;srcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;"**/*.ts"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="n"&gt;module_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@dataform/core"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s"&gt;"//protos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"@npm//@types/moo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"@npm//@types/node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"@npm//moo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"@npm//protobufjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This rule in the &lt;code&gt;BUILD&lt;/code&gt; file tells Bazel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The library called &lt;code&gt;core&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It should include all &lt;code&gt;.ts&lt;/code&gt; files within this folder&lt;/li&gt;
&lt;li&gt;It's (node) module name is &lt;code&gt;@dataform/core&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It has one internal dependency &lt;code&gt;//protos&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It has a few NPM dependencies, just like a &lt;code&gt;package.json&lt;/code&gt; file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To build this TS library you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;bazel build core
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: &lt;code&gt;ts_library&lt;/code&gt; rules and other node related rulesets are not core to the bazel runtime but are imported from elsewhere. You can read more about them here: &lt;a href="https://github.com/bazelbuild/rules_nodejs"&gt;https://github.com/bazelbuild/rules_nodejs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependencies between packages
&lt;/h2&gt;

&lt;p&gt;If you've worked with a NPM based monorepo before, you've probably used a tool like &lt;a href="https://github.com/lerna/lerna"&gt;Lerna&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lerna makes it easy to link packages locally so you can test changes across multiple NPM packages. It also makes it easier to manage versioning between them. &lt;strong&gt;We want that&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Bazel builds and &lt;em&gt;links&lt;/em&gt; packages without going anywhere near an actual NPM package. In our &lt;a href="https://github.com/dataform-co/dataform/tree/master/core"&gt;@dataform/core&lt;/a&gt; example above, the &lt;code&gt;ts_library&lt;/code&gt; rule depends on &lt;code&gt;//protos&lt;/code&gt; which is &lt;strong&gt;&lt;a href="https://github.com/dataform-co/dataform/blob/master/protos/BUILD"&gt;just another &lt;code&gt;ts_library&lt;/code&gt; rule&lt;/a&gt;&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ts_library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"protos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;srcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;"index.ts"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="n"&gt;module_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@dataform/protos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ts_libary&lt;/code&gt; rule does some magic to make sure that built packages are available under the &lt;code&gt;module_name&lt;/code&gt; attribute provided, which matches the NPM package they will be published at.&lt;/p&gt;

&lt;p&gt;So in our &lt;code&gt;@dataform/core&lt;/code&gt; package, we can import from the &lt;code&gt;//protos&lt;/code&gt; package whose &lt;code&gt;module_name&lt;/code&gt; is &lt;code&gt;@dataform/protos&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dataform&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@dataform/protos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we publish to NPM, these imports will resolve correctly too as the module names match the package names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing multiple packages
&lt;/h2&gt;

&lt;p&gt;Lerna also helps you manage multiple &lt;code&gt;package.json&lt;/code&gt; files, updating all versions together and publishing them. We would like a way to do the same thing in Bazel.&lt;/p&gt;

&lt;p&gt;To generate &lt;code&gt;package.json&lt;/code&gt; files we built small tool &lt;a href="https://github.com/dataform-co/dataform/tree/master/tools/json-merge"&gt;in our monorepo&lt;/a&gt; that Bazel uses to generate &lt;code&gt;package.json&lt;/code&gt; files using layers of JSON templates and string substitutions.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;@dataform/core&lt;/code&gt; package, we have a &lt;code&gt;core.package.json&lt;/code&gt; file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@dataform/core"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dataform core API."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.d.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@dataform/protos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$DF_VERSION"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^0.5.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"protobufjs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^6.8.8"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Any extra info, licenses, homepage etc - is inherited from the base &lt;a href="https://github.com/dataform-co/dataform/blob/master/common.package.json"&gt;&lt;code&gt;common.package.json&lt;/code&gt;&lt;/a&gt; so we don't have to keep several files in sync.&lt;/p&gt;

&lt;p&gt;The special string &lt;code&gt;$DF_VERSION&lt;/code&gt; gets replaced with a global constant defined as part of the Bazel build system in &lt;a href="https://github.com/dataform-co/dataform/blob/master/version.bzl"&gt;&lt;code&gt;version.bzl&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building NPM packages
&lt;/h2&gt;

&lt;p&gt;To evaluate these JSON templates, we wrote a Bazel &lt;a href="https://docs.bazel.build/versions/master/skylark/macros.html"&gt;macro&lt;/a&gt; to invoke our tool above and we invoke it in the &lt;code&gt;@dataform/core&lt;/code&gt; &lt;code&gt;BUILD&lt;/code&gt; file like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"//tools/npm:package.bzl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dataform_npm_package"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;dataform_npm_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;package_layers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s"&gt;"//:common.package.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"core.package.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;":core"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This &lt;a href="https://github.com/dataform-co/dataform/blob/master/tools/npm/package.bzl"&gt;custom bazel macro&lt;/a&gt; both generates a final &lt;code&gt;package.json&lt;/code&gt; from two templates listed, and creates an output dist folder with the compiled TypeScript that's ready to be published.&lt;/p&gt;

&lt;p&gt;To see the output of this and the final generated &lt;code&gt;package.json&lt;/code&gt;, you can run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;bazel build core:package
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Bazel tells us it's put the package in the folder &lt;code&gt;bazel-bin/core/package&lt;/code&gt; with &lt;code&gt;.js&lt;/code&gt; and &lt;code&gt;.d.ts&lt;/code&gt; files as well as the final &lt;code&gt;package.json&lt;/code&gt; (this is kind of like a &lt;code&gt;dist&lt;/code&gt; folder) that is ready to publish!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: We haven't fully automated this step yet, and it's still necessary to make sure that the dependencies and package name in the &lt;code&gt;BUILD&lt;/code&gt; file match those in the &lt;code&gt;package.json&lt;/code&gt; template, but it's certainly feasible to automate this entirely.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing NPM packages
&lt;/h2&gt;

&lt;p&gt;Publishing is easy at this point, and the &lt;code&gt;rules_nodejs&lt;/code&gt; libraries have this built in. To publish a package we can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;bazel run //core:package.publish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We still have a bash script to do this for all packages but all it does is invoke Bazel commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Test all the things.&lt;/span&gt;

bazel &lt;span class="nb"&gt;test&lt;/span&gt; //...

&lt;span class="c"&gt;# Publish all the things.&lt;/span&gt;

bazel run //api:package.publish
bazel run //core:package.publish
bazel run //cli:package.publish
bazel run //crossdb:package.publish
bazel run //protos:package.publish

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This is by no means a complete solution yet, and in reality will require you to learn quite a lot about Bazel to get it working on your own project. For anyone trying, hopefully our &lt;a href="https://github.com/dataform-co/dataform"&gt;repo&lt;/a&gt; can serve as a good reference!&lt;/p&gt;

&lt;p&gt;Despite that, I hope that this demonstrates that using Bazel is a great solution to managing complex projects and many Node / Typescript packages inside a single repository.&lt;br&gt;
With a a few small extra Bazel rules you can build a TypeScript monorepo that is lighting fast and will scale as your project does.&lt;/p&gt;

&lt;p&gt;If you found this post interesting and would be interested in a Bazel TypeScript starter pack repo, reach out and we'll see what we can do!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>bazel</category>
      <category>devops</category>
      <category>npm</category>
    </item>
    <item>
      <title>Typed CSS modules with Bazel</title>
      <dc:creator>Lewis Hemens</dc:creator>
      <pubDate>Sun, 23 Jun 2019 13:58:09 +0000</pubDate>
      <link>https://forem.com/lewish/typed-css-modules-with-bazel-3133</link>
      <guid>https://forem.com/lewish/typed-css-modules-with-bazel-3133</guid>
      <description>&lt;p&gt;If you are building TypeScript with &lt;a href="https://bazel.build"&gt;Bazel&lt;/a&gt; and using CSS, you probably want it to be typed. Tools such as Webpack can generate &lt;code&gt;.d.ts&lt;/code&gt; files for you, however Webpack plugins do this by adding the &lt;code&gt;.d.ts&lt;/code&gt; files to your source directory.&lt;/p&gt;

&lt;p&gt;This is a no go in Bazel, where you generally can't / shouldn't mutate the source during a build step.&lt;/p&gt;

&lt;p&gt;This can be accomplished fairly easily with a custom build rule.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: the following has been tested with Bazel 0.26.1 and &lt;a href="https://github.com/bazelbuild/rules_nodejs"&gt;rules_nodejs&lt;/a&gt; 0.31.1.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;All the following code snippets are from an open-source project and you can see them working in their entirety here: &lt;a href="https://github.com/dataform-co/dataform"&gt;https://github.com/dataform-co/dataform&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Assume we have a CSS file called &lt;code&gt;styles.css&lt;/code&gt; containing the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.someClass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.someClass&lt;/span&gt; &lt;span class="nc"&gt;.someOtherClass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And we want to generate a &lt;code&gt;styles.css.d.ts&lt;/code&gt; that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;someClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;someOtherClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So that we can provide this as an input to any consuming rules such as &lt;code&gt;ts_library&lt;/code&gt; to make sure we aren't using any class names that don't exist and to keep the TS compiler happy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;typed-css-modules&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;We can do this using the &lt;code&gt;typed-css-modules&lt;/code&gt; package.&lt;/p&gt;

&lt;p&gt;First we want to set up a &lt;code&gt;nodejs_binary&lt;/code&gt; that we can use to generate these typings.&lt;/p&gt;

&lt;p&gt;Firstly add this to your root &lt;code&gt;package.json&lt;/code&gt; assuming you manage your projects dependencies with &lt;code&gt;rules_nodejs&lt;/code&gt; and &lt;code&gt;npm_install/yarn_install&lt;/code&gt; rules, then add the following to a &lt;code&gt;BUILD&lt;/code&gt; file somewhere in your repo (we keep this in the root &lt;code&gt;BUILD&lt;/code&gt; file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;nodejs_binary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tcm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s"&gt;"@npm//typed-css-modules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;entry_point&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@npm//node_modules/typed-css-modules:lib/cli.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This allows us to run the TCM CLI from within Bazel rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the Bazel rule
&lt;/h2&gt;

&lt;p&gt;Now we need to define a Bazel rule that can actually run the TCM CLI and generate typings files. Add a new file called &lt;code&gt;css_typings.bzl&lt;/code&gt; somewhere in your repository with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_impl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;outs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;srcs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Only create outputs for css files.
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;".css"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Only .css file inputs are allowed."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;declare_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;".css.d.ts"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sibling&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;outs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tool&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;executable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"-o"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-p"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"--silent"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;progress_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Generating CSS type definitions for %s"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Return a structure that is compatible with the deps[] of a ts_library.
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;typescript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;declarations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;transitive_declarations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;type_blacklisted_declarations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depset&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;es5_sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depset&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;es6_sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depset&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;transitive_es5_sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depset&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;transitive_es6_sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depset&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;css_typings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_impl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"srcs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"css files"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allow_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s"&gt;"_tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;executable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;allow_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"//:tcm"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is a fairly simple Bazel build rule that generates CSS typings using the TCM CLI we installed previously.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Iterates through each input CSS file&lt;/li&gt;
&lt;li&gt;Runs the TCM tool against the CSS file to generate typings&lt;/li&gt;
&lt;li&gt;Returns something that is compatible as an input to &lt;code&gt;ts_library&lt;/code&gt; rules and can be added as a dependency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You will need to replace the &lt;code&gt;Label("//:tcm")&lt;/code&gt; line with the name of the &lt;code&gt;nodejs_binary&lt;/code&gt; rule you created in the previous step.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;Here is a simple example &lt;code&gt;BUILD&lt;/code&gt; file that uses this rule, assuming you put the &lt;code&gt;css_typings.bzl&lt;/code&gt; file in a &lt;code&gt;tools&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;filegroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;srcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;"**/*.css"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"//tools:css_typings.bzl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"css_typings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;css_typings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"css_typings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;srcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;":css"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@npm_bazel_typescript//:index.bzl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ts_library"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ts_library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"components"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;srcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s"&gt;"**/*.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"**/*.tsx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s"&gt;":css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s"&gt;":css_typings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You probably still need to actually bundle the CSS files using the &lt;code&gt;data&lt;/code&gt; attribute above, assuming that this library is going to bundled for web by some downstream consumer.&lt;/p&gt;

&lt;p&gt;And that should be all you need to generate CSS typings with Bazel Typescript projects!&lt;/p&gt;

&lt;p&gt;If this was useful to you, please let me know and I'll put it into a repository so it can be imported and used without the copy-paste.&lt;/p&gt;

</description>
      <category>bazel</category>
      <category>css</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
