<?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>DEV Community: Huy Nguyen</title>
    <description>The latest articles on DEV Community by Huy Nguyen (@huy).</description>
    <link>https://dev.to/huy</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%2F118897%2F266fad70-c263-46ec-b684-d3b7d7d30d9f.jpeg</url>
      <title>DEV Community: Huy Nguyen</title>
      <link>https://dev.to/huy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/huy"/>
    <language>en</language>
    <item>
      <title>Using Yarn v2 workspace, Docker Compose and Visual Studio Code to facilitate development of JavaScript monorepos</title>
      <dc:creator>Huy Nguyen</dc:creator>
      <pubDate>Thu, 07 May 2020 15:37:00 +0000</pubDate>
      <link>https://dev.to/huy/using-yarn-v2-workspace-docker-compose-and-visual-studio-code-to-facilitate-development-of-javascript-monorepos-2n28</link>
      <guid>https://dev.to/huy/using-yarn-v2-workspace-docker-compose-and-visual-studio-code-to-facilitate-development-of-javascript-monorepos-2n28</guid>
      <description>&lt;p&gt;&lt;em&gt;Note: This article originally &lt;a href="https://www.huy.dev/yarn-v2-workspace-docker-vs-code-2020-03-23/" rel="noopener noreferrer"&gt;appeared on my blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This article describes the tooling setup for a local development environment I recently created that brings together the following tools and technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The following features of &lt;a href="https://yarnpkg.com/" rel="noopener noreferrer"&gt;Yarn v2&lt;/a&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://yarnpkg.com/features/workspaces" rel="noopener noreferrer"&gt;workspaces&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://yarnpkg.com/features/pnp" rel="noopener noreferrer"&gt;Plug'n'Play&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The following features of &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt;:

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://code.visualstudio.com/docs/remote/containers" rel="noopener noreferrer"&gt;Remote Container Extension&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.visualstudio.com/docs/editor/intellisense#_intellisense-features" rel="noopener noreferrer"&gt;IntelliSense&lt;/a&gt; provided by the TypeScript language server.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode" rel="noopener noreferrer"&gt;Prettier formatter extension&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt; (and by extension, JavaScript, of which TypScript is a strict superset).&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;Webpack&lt;/a&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://babeljs.io/" rel="noopener noreferrer"&gt;Babel&lt;/a&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://prettier.io/" rel="noopener noreferrer"&gt;Prettier&lt;/a&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://jestjs.io/" rel="noopener noreferrer"&gt;Jest&lt;/a&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The article only provides a high-level overview because I have provided extensive commentary inside the codebase on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/huy-nguyen" rel="noopener noreferrer"&gt;
        huy-nguyen
      &lt;/a&gt; / &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code" rel="noopener noreferrer"&gt;
        yarn-v2-workspace-docker-vs-code
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A technology demonstration of the Plug'n'Play and workspace features of Yarn v2, Docker Compose, Visual Studio Code, TypeScript, Webpack, Babel, ESLint, Prettier and Jest
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;This repo uses a simple full-stack JavaScript web application to demonstrate the tooling setup for a local development environtment that brings together the following tools and technologies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.docker.com/compose/" rel="nofollow noopener noreferrer"&gt;Docker Compose&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The following features of &lt;a href="https://yarnpkg.com/" rel="nofollow noopener noreferrer"&gt;Yarn v2&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://yarnpkg.com/features/workspaces" rel="nofollow noopener noreferrer"&gt;workspaces&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://yarnpkg.com/features/pnp" rel="nofollow noopener noreferrer"&gt;Plug'n'Play&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The following features of &lt;a href="https://code.visualstudio.com/" rel="nofollow noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://code.visualstudio.com/docs/remote/containers" rel="nofollow noopener noreferrer"&gt;Remote Container Extension&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.visualstudio.com/docs/editor/intellisense#_intellisense-features" rel="nofollow noopener noreferrer"&gt;IntelliSense&lt;/a&gt; provided by the TypeScript language server.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode" rel="nofollow noopener noreferrer"&gt;Prettier formatter extension&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.typescriptlang.org/" rel="nofollow noopener noreferrer"&gt;TypeScript&lt;/a&gt; (and by extension, JavaScript, of which TypScript is a strict superset).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://webpack.js.org/" rel="nofollow noopener noreferrer"&gt;Webpack&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://babeljs.io/" rel="nofollow noopener noreferrer"&gt;Babel&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://eslint.org/" rel="nofollow noopener noreferrer"&gt;ESLint&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://prettier.io/" rel="nofollow noopener noreferrer"&gt;Prettier&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jestjs.io/" rel="nofollow noopener noreferrer"&gt;Jest&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The demo application is an interactive in-browser explorer of the &lt;a href="https://swapi.co/" rel="nofollow noopener noreferrer"&gt;Star Wars API&lt;/a&gt;, which contains the data about all the Star Wars films, characters, spaceships and so on
If you want to quickly try the application out, here is the &lt;a href="http://graphql.org/swapi-graphql" rel="nofollow noopener noreferrer"&gt;online version created by the GraphQL team&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For a high-level tour of this repo, please see the &lt;a href="https://www.huy.dev/yarn-v2-workspace-docker-vs-code-2020-03-23/" rel="nofollow noopener noreferrer"&gt;accompanying blog post&lt;/a&gt;…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The intended audience are experienced developers who have used most of the above tools and technologies in isolation and are looking to wrangle them together.&lt;br&gt;
Even though this article only discusses TypeScript/JavaScript, I've created another repo showing how to set up a Docker Compose-based development and debugging environment for Python using VS Code's Remote Container Extension:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/huy-nguyen" rel="noopener noreferrer"&gt;
        huy-nguyen
      &lt;/a&gt; / &lt;a href="https://github.com/huy-nguyen/vs-code-docker-python-remote-debugging" rel="noopener noreferrer"&gt;
        vs-code-docker-python-remote-debugging
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Remote debugging for Python from one container to another in VS Code
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;This is a fairly minimal setup that demonstrates remote debugging for Python from one container to another with VS Code using a combination of the official &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-python.python" rel="nofollow noopener noreferrer"&gt;VS Code Python extension&lt;/a&gt; and the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers" rel="nofollow noopener noreferrer"&gt;Remote Container development extension&lt;/a&gt;
This is useful because sometimes I don't want to run the editor on my host machine directly but still want to be able to debug a Python script running in another Docker container
For example, my Mac machine may have Python 3 built-in but I want to debug a script that runs Python 2.&lt;/p&gt;
&lt;p&gt;This setup has been tested with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VS Code: version 1.40.2&lt;/li&gt;
&lt;li&gt;Python extension: version 2019.11.50794&lt;/li&gt;
&lt;li&gt;Remote Container extension: 0.86.0&lt;/li&gt;
&lt;li&gt;Docker Desktop for Mac 2.1.0.5&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are two containers in this setup that each have a volume pointing to the repo's directory:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;editor&lt;/code&gt; runs the VS Code editor
This is a bare bone Python image because I don't need that much…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/huy-nguyen/vs-code-docker-python-remote-debugging" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Because I'll most likely find ways to improve this setup when I use it for my own development work and because the integration between the various tools in this setup is constantly improving, I expect to revise this article multiple times in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;This setup facilitates the development of JavaScript monorepos, which promote a high degree of coherence and consistency between various parts of a codebase in terms of tooling, dependencies and business logic.&lt;br&gt;
For example, in a library like Babel, coherence between the core processing pipeline (&lt;code&gt;@babel/parser&lt;/code&gt;, &lt;code&gt;@babel/generator&lt;/code&gt;), input/output adapters (such as &lt;code&gt;@babel/cli&lt;/code&gt;) and plugins (&lt;code&gt;@babel/@preset-env&lt;/code&gt;) ensures that the entire ecosystem moves in lock step when new JavaScript language features are released.&lt;br&gt;
In particular, isomorphic full-stack web applications (single-page front-end React applications that use server-side rendering and client-side hydration) that follow the &lt;a href="https://12factor.net/" rel="noopener noreferrer"&gt;12-factor app philosophy&lt;/a&gt; greatly benefit from this setup.&lt;br&gt;
The monorepo structure ensures that the front- and back-ends both use the same version of React to avoid hydration failure due to version mismatch.&lt;/p&gt;

&lt;p&gt;The ability to develop out of Docker containers with the same ease of doing it on the host machine enables the local development environment to closely resemble the production environment, achieving &lt;a href="https://12factor.net/dev-prod-parity" rel="noopener noreferrer"&gt;dev/prod parity&lt;/a&gt;.&lt;br&gt;
Because JavaScript dependencies and system tools needed by the app can be vendored into the Docker image and Docker Compose configurations, no implicit dependencies "leak in" from the surrounding environment.&lt;br&gt;
As an added bonus, this &lt;a href="https://12factor.net/dependencies" rel="noopener noreferrer"&gt;complete capture of all environmental dependencies&lt;/a&gt; ensures consistency in the development environment of current developers and simplifies the onboarding of new developers.&lt;br&gt;
Instead of running many configuration commands to configure the local machines' environments (where each command can produce different results or errors due to differences between individual machines), new developers can check out the code base, run a few Docker commands that have predictable results and start developing right away in a highly uniform and controlled environment of Docker containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo application
&lt;/h2&gt;

&lt;p&gt;To show how this setup works at a practical level, a demo JavaScript full-stack web application is included.&lt;br&gt;
This demo application is an interactive in-browser explorer of the &lt;a href="https://swapi.co/" rel="noopener noreferrer"&gt;Star Wars API&lt;/a&gt;, which contains the data about all the Star Wars films, characters, spaceships and so on.&lt;br&gt;
If you want to quickly try the application out, here is the &lt;a href="http://graphql.org/swapi-graphql" rel="noopener noreferrer"&gt;online version created by the GraphQL team&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In keeping with the purpose of this article, the demo application's functionality is deliberately kept simple to avoid distracting from the tooling.&lt;br&gt;
All the application code is contained in only three files (&lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/packages/front-end/src/App.tsx" rel="noopener noreferrer"&gt;&lt;code&gt;front-end/src/App.tsx&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/packages/front-end/src/index.tsx" rel="noopener noreferrer"&gt;&lt;code&gt;front-end/src/index.tsx&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/packages/back-end/src/index.ts" rel="noopener noreferrer"&gt;&lt;code&gt;back-end/src/index.ts&lt;/code&gt;&lt;/a&gt;).&lt;br&gt;
The rest are tooling configurations.&lt;br&gt;
As a believer in a minimalist approach to tooling configurations, I have made every attempt to only include the absolute minimum amount of configuration necessary for the functionality of these tools.&lt;/p&gt;

&lt;p&gt;The front-end's run time dependencies are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;react&lt;/code&gt; and &lt;code&gt;react-dom&lt;/code&gt; are the UI framework.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;graphiql&lt;/code&gt; provides the GraphQL interactive explorer.
It has two peer dependencies that are included but not used directly: &lt;code&gt;prop-types&lt;/code&gt; and &lt;code&gt;graphql&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The back-end's run time dependencies are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;express&lt;/code&gt; is the web server.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;express-graphql&lt;/code&gt; is the Express middleware for GraphQL.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;swapi-graphql-schema&lt;/code&gt; contains the GraphQL schema and resolver.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;graphql&lt;/code&gt; is a peer dependency of &lt;code&gt;swapi-graphql-schema&lt;/code&gt; and &lt;code&gt;express-graphql&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cors&lt;/code&gt; takes care of CORS issues in local development.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Codebase structure
&lt;/h2&gt;

&lt;p&gt;The code base is structured as a monorepo containing two Yarn workspaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;front-end&lt;/code&gt; workspace (&lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/tree/master/packages/front-end" rel="noopener noreferrer"&gt;&lt;code&gt;packages/front-end&lt;/code&gt;&lt;/a&gt;) is a React application that contains a single &lt;a href="https://github.com/graphql/graphiql/tree/master/packages/graphiql" rel="noopener noreferrer"&gt;GraphiQL&lt;/a&gt; component, which provides the interactive user interface.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;back-end&lt;/code&gt; workspace (&lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/tree/master/packages/back-end" rel="noopener noreferrer"&gt;&lt;code&gt;packages/back-end&lt;/code&gt;&lt;/a&gt;) is an Express server that serves up a GraphQL API using the &lt;a href="https://github.com/graphql/express-graphql" rel="noopener noreferrer"&gt;&lt;code&gt;express-graphql&lt;/code&gt;&lt;/a&gt; middleware.
This GraphQL API is a wrapper around the original REST-ful Star Wars API.
The &lt;a href="https://github.com/huy-nguyen/swapi-graphql" rel="noopener noreferrer"&gt;GraphQL schema and resolvers&lt;/a&gt; has been forked and improved (tooling-wise) by me to make it possible to use inside this application.
In keeping with the 12-factor philosophy, the back-end remains agnostic of its surrounding by listening on a single port (8000) and listening at the root path (&lt;code&gt;/&lt;/code&gt;) instead of &lt;code&gt;/api&lt;/code&gt; or &lt;code&gt;/graphql&lt;/code&gt;.
Note that although &lt;code&gt;express-graphql&lt;/code&gt; is capable of serving up its own GraphiQL editor (by &lt;a href="https://github.com/graphql/express-graphql/blob/d634e419663fb8e3133fe5fada47809192e6e5e2/README.md#simple-setup" rel="noopener noreferrer"&gt;setting the &lt;code&gt;graphiql&lt;/code&gt; option to &lt;code&gt;true&lt;/code&gt;&lt;/a&gt;), thereby removing the need for a separate front-end, I choose to create a separate front-end for demonstration purpose.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Yarn v2
&lt;/h2&gt;

&lt;p&gt;Version 2 of the Yarn package manager, currently in relase candidate status, brings genuine innovations to the JavaScript package system, many of which are covered in its &lt;a href="https://dev.to/arcanis/introducing-yarn-2-4eh1"&gt;release announcement&lt;/a&gt;.&lt;br&gt;
Two of those innovations are noteworthy.&lt;/p&gt;

&lt;p&gt;The first innovation is &lt;a href="https://yarnpkg.com/features/workspaces" rel="noopener noreferrer"&gt;workspaces&lt;/a&gt;, which was first introduced in version 1 but only becomes truly usefeul for Docker in version 2.&lt;br&gt;
By allowing developers to transparently work with monorepos, workspaces provide a high degree of coherence to a project because multiple related parts can exist in the same monorepo and cross reference each other, allowing any changes in one part to be instantly applied to the others.&lt;/p&gt;

&lt;p&gt;Prior to Yarn v2, it was not possible to fully Dockerize a workspace-based monorepo because workspace was implemented with symlinks, which &lt;a href="https://github.com/moby/moby/issues/1676" rel="noopener noreferrer"&gt;do not work in a Docker image&lt;/a&gt;.&lt;br&gt;
However, Yarn v2 workspaces do not use symlinks.&lt;br&gt;
Instead, they use &lt;a href="https://yarnpkg.com/features/pnp" rel="noopener noreferrer"&gt;Plug'n'Play (PnP)&lt;/a&gt;, the second innovation I want to mention.&lt;br&gt;
Roughly speaking, PnP modifies NodeJS's module resolution strategy so that when Node &lt;code&gt;require&lt;/code&gt;s a module, instead of &lt;a href="https://nodejs.org/api/modules.html#modules_all_together" rel="noopener noreferrer"&gt;making multiple file system calls&lt;/a&gt; to look for that module, Node is directed by Yarn to to look in a central Yarn cache and can obtain that module with a single call.&lt;br&gt;
This vastly improves the performance during installation and runtime module loading.&lt;br&gt;
However, this feature does interfere with the working of two kind of development tools: those whose dependency loading depends on file layout on disk instead of proper declaration of dependencies in their &lt;code&gt;package.json&lt;/code&gt; (e.g. &lt;code&gt;fork-ts-checker-webpack-plugin&lt;/code&gt;) or tools that implement their own module resolution strategy (e.g. &lt;code&gt;eslint&lt;/code&gt;).&lt;br&gt;
When I use the term Yarn v2 workspace in this article, I mean workspace &lt;em&gt;and&lt;/em&gt; PnP.&lt;/p&gt;

&lt;p&gt;Note that although you will see a few &lt;code&gt;node_modules&lt;/code&gt; directories appear after running &lt;code&gt;yarn build&lt;/code&gt; or &lt;code&gt;yarn develop&lt;/code&gt; in a workspace, these directories do not contain library code.&lt;br&gt;
Rather, they contain the local cache of &lt;a href="https://github.com/terser/terser" rel="noopener noreferrer"&gt;Terser&lt;/a&gt;, Webpack's default code minifier.&lt;br&gt;
With PnP, no module code is loaded from &lt;code&gt;node_modules&lt;/code&gt; directories.&lt;/p&gt;

&lt;p&gt;This setup also includes the &lt;a href="https://github.com/yarnpkg/berry/tree/master/packages/plugin-workspace-tools" rel="noopener noreferrer"&gt;&lt;code&gt;@yarnpkg/plugin-workspace-tools&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
The plugin allows execution of a &lt;code&gt;yarn&lt;/code&gt; task across multiple workspaces e.g. &lt;code&gt;yarn workspaces foreach build&lt;/code&gt; will run the &lt;code&gt;yarn build&lt;/code&gt; task for each workspace.&lt;br&gt;
It is not absolutely essential for the functioning of the setup but is nevertheless added to demonstrate how to include Yarn plugins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker image
&lt;/h2&gt;

&lt;p&gt;As stated in the motivation section, the Docker image was created with the core principle that it should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capture within the image as much of the npm dependencies, Yarn settings and system configuration as possible that are necessary to develop and run the code using Yarn v2 inside the container, and&lt;/li&gt;
&lt;li&gt;Read and write as little as possible from and to the codebase's directory.
In fact, the only information the Docker build process reads from the codebase is the root &lt;code&gt;yarn.lock&lt;/code&gt; and the &lt;code&gt;package.json&lt;/code&gt;s inside the root and child workspaces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Docker image achieves this by taking full advantage of Yarn v2's high configurability by providing two configuration files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/Dockerfile#L91-L102" rel="noopener noreferrer"&gt;&lt;code&gt;.yarnrc.yml&lt;/code&gt;&lt;/a&gt; (Yarn v2's main configuration file) is "lifted" out of the codebase volume mount at &lt;code&gt;/srv/app&lt;/code&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt; into its parent directory &lt;code&gt;/srv&lt;/code&gt; (as seen from the container's side).
Because &lt;code&gt;.yarnrc.yml&lt;/code&gt; is only used by Yarn v2, not Yarn v1 or npm, the lifting of &lt;code&gt;.yarnrc.yml&lt;/code&gt; into the parent directory still allows Yarn v2 on the container side to find its configuration while not interfering with the operation of npm or Yarn v1 on the host side.
This &lt;code&gt;.yarnrc.yml&lt;/code&gt; contains important settings to further isolate Yarn v2 from the codebase directory:

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;cacheFolder&lt;/code&gt;, &lt;code&gt;globalFolder&lt;/code&gt;, &lt;code&gt;virtualFolder&lt;/code&gt; and &lt;code&gt;pnpUnpluggedFolder&lt;/code&gt; settings tell Yarn to store the npm dependencies into a &lt;code&gt;/yarn&lt;/code&gt; directory outside the codebase directory instead of into the default &lt;code&gt;.yarn&lt;/code&gt; directory inside the codebase directory.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;plugins&lt;/code&gt; settings tells Yarn where to find its plugins, which have already been downloaded and stored into &lt;code&gt;/yarn&lt;/code&gt; by a previous build step.
As mentioned above, the only plugin I use is &lt;code&gt;@yarnpkg/workspace-tools&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;A &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/Dockerfile#L67-L69" rel="noopener noreferrer"&gt;&lt;code&gt;.yarnrc&lt;/code&gt;&lt;/a&gt; file (different from &lt;code&gt;.yarnrc.yml&lt;/code&gt;) that contains the &lt;code&gt;yarn-path&lt;/code&gt; setting, which instructs the system-wide Yarn, which can either be Yarn v1 or v2 (in this case v1 because that's the version that's shipped in the base NodeJS Docker image), where to find the Yarn executable for the current codebase.
Because there's only one codebase inside the Docker container and the container is run under the non-root user &lt;code&gt;node&lt;/code&gt; &lt;a href="https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user" rel="noopener noreferrer"&gt;per best practice&lt;/a&gt;, I've chosen to put this &lt;code&gt;.yarnrc&lt;/code&gt; file in the &lt;code&gt;node&lt;/code&gt; user's home directory.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The only Yarn-related files that are not captured in the Docker image are &lt;code&gt;yarn.lock&lt;/code&gt; and &lt;code&gt;.pnp.js&lt;/code&gt; because their locations are not configurable.&lt;br&gt;
The presence of these two files in the codebase directory means that it's not possible to use both Yarn v2 on the container side and Yarn v1 on the host side although there currently is &lt;a href="https://github.com/yarnpkg/berry/issues/458" rel="noopener noreferrer"&gt;a ticket to to fix that&lt;/a&gt;.&lt;br&gt;
However, it should be possible to use Yarn v2 on the container side and npm on the host side.&lt;/p&gt;

&lt;p&gt;Last but not least, just like all npm dependencies in this codebase, the version of Yarn and its plugins as well as the tags of all base Docker images are pinned down exactly to ensure development environment stability and reproducibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Compose services
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;docker-compose.yml&lt;/code&gt; file defines three services.&lt;br&gt;
One is &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/docker-compose.yml#L3-L19" rel="noopener noreferrer"&gt;&lt;code&gt;base&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
It saves me from typing out a long &lt;code&gt;docker build&lt;/code&gt; command with a long list of build arguments and tags the resulting Docker image with the same name that will be used by the other two services, &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/docker-compose.yml#L44-L64" rel="noopener noreferrer"&gt;&lt;code&gt;front-end&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/docker-compose.yml#L21-L41" rel="noopener noreferrer"&gt;&lt;code&gt;back-end&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note that both the &lt;code&gt;front-end&lt;/code&gt; and &lt;code&gt;back-end&lt;/code&gt; services share the same volume mounts.&lt;br&gt;
It's not surprising that they share the &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/docker-compose.yml#L33-L35" rel="noopener noreferrer"&gt;volume mount for the source code&lt;/a&gt;.&lt;br&gt;
The more remarkable thing is that they can also share &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/docker-compose.yml#L66-L70" rel="noopener noreferrer"&gt;same volume mount for the Yarn cache&lt;/a&gt;.&lt;br&gt;
Because of PnP, Yarn v2 is able to centralize all dependencies across all workspaces into a single cache directory regardless of whether the dependencies are shared or not shared among the workspaces.&lt;br&gt;
This was not possible for workspaces in v1 because Yarn v1 only "hoists" shared dependencies out of child workspaces' &lt;code&gt;node_modules&lt;/code&gt; directories into the &lt;code&gt;node_modules&lt;/code&gt; directory in the root workspace.&lt;br&gt;
If a Yarn v1 monorepo consists of 

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;nn&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 child workspaces, the same setup would requires 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;(n+1)(n + 1)&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 anonymous Docker volumes (one for each child workspace's &lt;code&gt;node_module&lt;/code&gt; plus one for the root's &lt;code&gt;node_modules&lt;/code&gt;) whereas a v2 monorepo only needs a single volume.&lt;br&gt;
Another disadvantage of Yarn v1 is that lifting &lt;code&gt;node_modules&lt;/code&gt; directories from within the codebase directory into a Docker container using volume mounts is unreliable due to &lt;a href="https://github.com/moby/moby/issues/26157" rel="noopener noreferrer"&gt;a long-running bug in Docker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here are a few useful Docker Compose commands in this codebase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To start the front-end and back-end containers in development mode, run &lt;code&gt;docker-compose up -d&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To tail the interleaved logs of the front-end and back-end containers, run &lt;code&gt;docker-compose logs --tail=all -f&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To shut down the web application, run &lt;code&gt;docker-compose down&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To delete the anonymous Docker volumes associated with this Docker Compose config (useful for troubleshooting), run:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker volume rm $$(docker volume ls -f "name=yarn-v2-workspace-docker-vs-code" --format "{{.Name}}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  VS Code's Remote Containers Extension
&lt;/h2&gt;

&lt;p&gt;The major enabling factor for my workflow is VS Code's &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers" rel="noopener noreferrer"&gt;Remote Containers extension&lt;/a&gt;.&lt;br&gt;
The extension allows you to have the same development experience inside a container as you would have on your host machine.&lt;br&gt;
As explained by this diagram from &lt;a href="https://code.visualstudio.com/docs/remote/containers" rel="noopener noreferrer"&gt;the extension's documentation&lt;/a&gt;, it works by installing a server (and other VS Code extensions if you so wish) into the Docker container while keeping the editor's user interface fully on the host side.&lt;br&gt;
Application source code is shared between the host and the container through a volume mount:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcode.visualstudio.com%2Fassets%2Fdocs%2Fremote%2Fcontainers%2Farchitecture-containers.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcode.visualstudio.com%2Fassets%2Fdocs%2Fremote%2Fcontainers%2Farchitecture-containers.png" alt="Visual Studio Code remote container extension architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This approach has two advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running the VS Code server and extensions inside the container guarantees that the way the code is executed in development the same way as it would be during production.
Furthermore, running Visual Studio Code extensions for important development tasks such as linting, type checking and debugging inside the container ensure that these tasks perform exactly the same way on different developers' machines.&lt;/li&gt;
&lt;li&gt;Keeping the editor's graphical interface on the host side helps maintain fast and responsive performance, avoiding the lag commonly associated with a remote desktop solution such as X11 forwarding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The importance of being able to develop from inside containers and the demonstrated usefulness of VS Code's Remote Containers Extension have recently prompted Facebook to retire their own unified development environment for the Atom editor in order to &lt;a href="https://developers.facebook.com/blog/post/2019/11/19/facebook-microsoft-partnering-remote-development/" rel="noopener noreferrer"&gt;join force with Microsoft&lt;/a&gt; to further develop VS Code's Remote Containers Extensions.&lt;br&gt;
There is currently an item on the extension's road map for &lt;a href="https://github.com/microsoft/vscode-remote-release/wiki/Roadmap#explorations" rel="noopener noreferrer"&gt;supporting Kubernetes&lt;/a&gt;, which will be very exciting if released.&lt;/p&gt;

&lt;p&gt;The configuration for a remote developer container is specified in a &lt;a href="https://code.visualstudio.com/docs/remote/containers#_devcontainerjson-reference" rel="noopener noreferrer"&gt;&lt;code&gt;devcontainer.json&lt;/code&gt; file&lt;/a&gt; in the project directory.&lt;br&gt;
However, because I have two containers, I need separate &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/vs-code-container-back-end/.devcontainer.json" rel="noopener noreferrer"&gt;&lt;code&gt;vs-code-container-back-end/.devcontainer.json&lt;/code&gt;&lt;/a&gt; for the back-end and &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/vs-code-container-front-end/.devcontainer.json" rel="noopener noreferrer"&gt;&lt;code&gt;vs-code-container-front-end/.devcontainer.json&lt;/code&gt;&lt;/a&gt; for the front-end&lt;br&gt;
(note the leading dots in the file names).&lt;br&gt;
There are a few important settings in these files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dockerComposeFile&lt;/code&gt;: an array of one or more &lt;code&gt;docker-compose.yml&lt;/code&gt; files, including overrides.
For example, &lt;code&gt;front-end.docker-compose.yml&lt;/code&gt; overrides certain settings specific to the front-end service in the base &lt;code&gt;docker-compose.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;service&lt;/code&gt;: which service VS Code should attach itself to for remote development.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;extensions&lt;/code&gt;: a list of any VS Code extensions that should be installed into the container.
Although some VS Code extensions run on the host side (the &lt;a href="https://github.com/VSCodeVim/Vim" rel="noopener noreferrer"&gt;Vim plugin&lt;/a&gt; for example), most are run in the remote container and thus has to be specified here.
In this codebase, the Prettier extension is installed into the development containers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I suggest that you exclude the files mentioned in this section from version control in a real-world project because they mostly contain settings that are more personal to each developer, such as what VS Code extensions to install or what Linux shell to use.&lt;br&gt;
This is in contrast to &lt;code&gt;Dockerfile&lt;/code&gt; and &lt;code&gt;docker-compose.yml&lt;/code&gt;, which need to be consistent across all developers because they are important to the correct functioning of the codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript
&lt;/h2&gt;

&lt;p&gt;TypeScript is chosen as the programming language to show how it can work seamlessly with Yarn v2 workspace and VS Code with some configuration.&lt;br&gt;
If you choose to use JavaScript, things only get easier and you can ignore the discussion in this section.&lt;br&gt;
Putting aside editor integration for a moment, using TypeScript, as usual, requires installing the &lt;code&gt;typescript&lt;/code&gt; package and type definitions for libraries that don't already include them, such as &lt;code&gt;@types/react&lt;/code&gt; and &lt;code&gt;@types/react-dom&lt;/code&gt;.&lt;br&gt;
Because Babel already supports workspace, I decide to use it to transpile from TypeScript to JavaScript through &lt;a href="https://devblogs.microsoft.com/typescript/typescript-and-babel-7/" rel="noopener noreferrer"&gt;@babel/preset-typescript&lt;/a&gt; for both Webpack and Jest.&lt;br&gt;
However, because Babel only transpile code without doing type checking, it's necessary to add a few more ingredients:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In development mode: &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/packages/back-end/webpack.config.js#L15-L17" rel="noopener noreferrer"&gt;add the &lt;code&gt;fork-ts-checker-webpack-plugin&lt;/code&gt;&lt;/a&gt; to the Webpack config, which watches TypeScript files and performs type checking on a separate thread.
This plugin does require &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/Dockerfile#L113-L115" rel="noopener noreferrer"&gt;extra settings in &lt;code&gt;.yarnrc.yml&lt;/code&gt;&lt;/a&gt; because it doesn't declare its peer dependencies properly.&lt;/li&gt;
&lt;li&gt;In production mode: run &lt;code&gt;tsc --noEmit&lt;/code&gt;, which invokes the TypeScript compiler to perform type checking without emitting any code, before starting a build with Webpack.
The &lt;code&gt;fork-ts-checker-webpack-plugin&lt;/code&gt; is not used in a production build because if type checking fails, I want the build task to also fail synchronously.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that you don't really need the &lt;a href="https://github.com/yarnpkg/berry/tree/master/packages/plugin-typescript" rel="noopener noreferrer"&gt;&lt;code&gt;@yarnpkg/typescript-plugin&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
If installed, it will helpfully install the corresponding type definitions from DefinitelyTyped for you when you &lt;code&gt;yarn install&lt;/code&gt; a package.&lt;br&gt;
However, I find this help a bit annoying because when I install a tooling dependency like Prettier, the plugin will also install the unnecessary type definition &lt;code&gt;@types/prettier&lt;/code&gt;.&lt;br&gt;
You can safely ignore this plugin if you manually install type definitions for your runtime dependencies.&lt;br&gt;
Thanksfully, VS Code's IntelliSense will definitely remind you in case you forget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Babel
&lt;/h2&gt;

&lt;p&gt;Babel works very well with workspace and no special configuration is needed.&lt;br&gt;
Here are the Babel-related dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@babel/core&lt;/code&gt; is the main Babel engine,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@babel/preset-env&lt;/code&gt; transpiles ES module to Common JS for Jest.
This is not necessary on the back-end.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@babel/preset-typescript&lt;/code&gt; transpiles TypeScript to JavaScript.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@babel/preset-react&lt;/code&gt; transpiles JSX in the front-end.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Webpack
&lt;/h2&gt;

&lt;p&gt;Webpack works quite well with PnP.&lt;br&gt;
The only changes I need to make in the Webpack config compared to a regular config are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Webpack config, add the &lt;code&gt;pnp-webpack-plugin&lt;/code&gt; as &lt;a href="https://github.com/arcanis/pnp-webpack-plugin/blob/659125cee0625f1a543bc1645e3917ad90857052/README.md#usage" rel="noopener noreferrer"&gt;instructed here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;In Webpack config, reference the &lt;code&gt;babel-loader&lt;/code&gt; as &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/packages/back-end/webpack.config.js#L34" rel="noopener noreferrer"&gt;&lt;code&gt;require.resolve("babel-loader")&lt;/code&gt;&lt;/a&gt; instead of just &lt;code&gt;"babel-loader"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/packages/back-end/package.json#L4" rel="noopener noreferrer"&gt;Invoke Webpack with &lt;code&gt;webpack-cli&lt;/code&gt;&lt;/a&gt; instead of &lt;code&gt;webpack&lt;/code&gt; due to a &lt;a href="https://github.com/yarnpkg/berry/issues/556" rel="noopener noreferrer"&gt;bug in Webpack&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The front-end is served in &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/packages/front-end/package.json#L9" rel="noopener noreferrer"&gt;development mode&lt;/a&gt; by &lt;code&gt;webpack-dev-server&lt;/code&gt; and in &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/packages/front-end/package.json#L8" rel="noopener noreferrer"&gt;production mode&lt;/a&gt; by the &lt;code&gt;serve&lt;/code&gt; package.&lt;/p&gt;

&lt;p&gt;Because the back-end is already a server, I don't use &lt;code&gt;webpack-dev-server&lt;/code&gt; in the back-end's Webpack development mode config.&lt;br&gt;
Rather, I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Webpack CLI's &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/packages/back-end/package.json#L9" rel="noopener noreferrer"&gt;&lt;code&gt;--watch&lt;/code&gt; mode&lt;/a&gt; to recompile when modified.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/packages/back-end/webpack.config.js#L18" rel="noopener noreferrer"&gt;&lt;code&gt;nodemon-webpack-plugin&lt;/code&gt;&lt;/a&gt; to run &lt;code&gt;nodemon&lt;/code&gt; on the build output and restart the server automatically when the output change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other dependencies necessary for Webpack's functionality are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;css-loader&lt;/code&gt; and &lt;code&gt;style-loader&lt;/code&gt; are Webpack loaders for CSS.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;html-webpack-plugin&lt;/code&gt; generates the index HTML file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prettier
&lt;/h2&gt;

&lt;p&gt;Prettier works well both standalone on the command line and integrated as a VS Code Plugin.&lt;br&gt;
Because I use the default Prettier settings, no configuration file is needed.&lt;br&gt;
However, a &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/packages/back-end/.prettierignore" rel="noopener noreferrer"&gt;&lt;code&gt;.prettierignore&lt;/code&gt;&lt;/a&gt; file is added to exclude certain directories, such as &lt;code&gt;node_modules&lt;/code&gt; and &lt;code&gt;coverage&lt;/code&gt; (Jest's code coverage output).&lt;br&gt;
The only dependency necessary is the package &lt;code&gt;prettier&lt;/code&gt; itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  ESLint
&lt;/h2&gt;

&lt;p&gt;ESLint takes a bit of work to work with workspaces.&lt;br&gt;
Running ESLint on the command line works fine except when I want to use preset configurations.&lt;br&gt;
Some shared configurations work out of the box, such as &lt;code&gt;plugin:react/recommended&lt;/code&gt; (the recommended config from &lt;code&gt;eslint-plugin-react&lt;/code&gt;).&lt;br&gt;
Other preset configs, such as &lt;code&gt;eslint:recommended&lt;/code&gt; or &lt;code&gt;prettier&lt;/code&gt;, require manually copying the configs into a local directory and telling ESLint where to find them with a relative path.&lt;br&gt;
Fortunately, because ESLint configs are either JSON files or CommonJS modules that export a plain JavaScript object, adapting them didn't take too much effort.&lt;br&gt;
However, I do look forward to the day I can just use these configs out of the box the way they are intended to.&lt;br&gt;
In the mean time, I'll monitor the relevant GitHub issues &lt;a href="https://github.com/yarnpkg/berry/issues/706" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/yarnpkg/berry/issues/8" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This codebase uses both Prettier and ESLint to demonstrate how the linting responsibility should be shared among these tools.&lt;br&gt;
Prettier checks for strictly code styling-related issues, such as trailing commas and quotes.&lt;br&gt;
ESLint checks for more substantive issues such as checking that &lt;code&gt;for&lt;/code&gt; loops' counter variables move in the right direction to avoid infinite loops.&lt;/p&gt;

&lt;p&gt;The dependencies required to run ESLint are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;eslint&lt;/code&gt; is the executable.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;eslint-plugin-react&lt;/code&gt; contains rules dealing with React and JSX.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@typescript-eslint/{eslint-plugin,parser}&lt;/code&gt; allows ESLint to work with TypeScript code.&lt;/li&gt;
&lt;li&gt;All the configurations contained in the &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/tree/master/eslint-configs" rel="noopener noreferrer"&gt;&lt;code&gt;eslint-configs&lt;/code&gt;&lt;/a&gt; directory.
The comments in them will tell you where they came from originally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  VS Code integration with TypeScript Intellisense and Prettier
&lt;/h2&gt;

&lt;p&gt;Getting Intellisense (provided by the TypeScript language service) and Prettier to work in VS Code wasn't too hard with &lt;a href="https://yarnpkg.com/advanced/pnpify" rel="noopener noreferrer"&gt;PnPify&lt;/a&gt;, a tool designed to smooth over incompatibility between PnP and various tools in the JavaScript ecosystem.&lt;br&gt;
Here are the &lt;a href="https://yarnpkg.com/advanced/editor-sdks#vscode" rel="noopener noreferrer"&gt;instructions&lt;/a&gt;.&lt;br&gt;
&lt;code&gt;typescript&lt;/code&gt; and &lt;code&gt;prettier&lt;/code&gt; have to be installed as a dev dependency in the top-level workspace.&lt;br&gt;
This is necessary because PnPify will scan the top-level &lt;code&gt;package.json&lt;/code&gt; to figure out what tools it needs to generate configurations for.&lt;br&gt;
Currently PnPify can generate configurations for TypeScript, Prettier and ESLint.&lt;br&gt;
These configurations, which essentially tell VS Code where to find executables that have been modified by PnPify for TypeScript and Prettier, are stored in the &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/tree/master/.vscode/pnpify" rel="noopener noreferrer"&gt;&lt;code&gt;.vscode/pnpify&lt;/code&gt;&lt;/a&gt; directory in the top-level workspace.&lt;br&gt;
These modified executable are wrappers around the real executables and perform some initial setup to make they play nice with PnP.&lt;br&gt;
The repo already contains the result of these steps so you don't have to run them.&lt;/p&gt;

&lt;p&gt;To run Prettier on a file, just choose "Format Document" from VS Code's Command Palette.&lt;/p&gt;

&lt;p&gt;I couldn't get the VS Code ESLint plugin to work but you can always run it on the command line.&lt;/p&gt;

&lt;p&gt;An outstanding issue with TypeScript IntelliSense is that hitting F12 ("Go to definition") on an identifier in a TypeScript file will not take me to its TypeScript definition.&lt;br&gt;
This is caused by Yarn's storage of npm packages as zip files (see &lt;a href="https://github.com/microsoft/vscode/issues/75559" rel="noopener noreferrer"&gt;GitHub issue&lt;/a&gt;) and a fix for it is on VS Code's road map.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jest
&lt;/h2&gt;

&lt;p&gt;Because I use Babel to transpile both TypeScript application code and test code, Jest works out of the box without any special setup.&lt;br&gt;
To demonstrate that testing with Jest works, the front-end has a single &lt;a href="https://github.com/huy-nguyen/yarn-v2-workspace-docker-vs-code/blob/master/packages/front-end/src/__tests__/App.tsx" rel="noopener noreferrer"&gt;smoke test&lt;/a&gt; to ensure that the UI renders without error into a JSDOM environment.&lt;br&gt;
The test uses &lt;a href="https://testing-library.com/docs/react-testing-library/intro" rel="noopener noreferrer"&gt;&lt;code&gt;@testing-library/react&lt;/code&gt;&lt;/a&gt; to ensure that user-facing text elements in the UI (such as the "Prettify" and "Merge" buttons) are present in the markup when rendered in this mock DOM.&lt;br&gt;
I might add more realistic tests such as visual regression tests with &lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt; in the future.&lt;/p&gt;

&lt;p&gt;Other installed dependencies that are necessary for testing are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;jest&lt;/code&gt; is the testing framework.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@testing-library/jest-dom&lt;/code&gt; adds Jest assertions on DOM elements, such as &lt;code&gt;.toBeInTheDocument()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@types/testing-library__{jest-dom,react}&lt;/code&gt; are the TypeScript definitions for the two &lt;code&gt;@testing-library&lt;/code&gt; packages because tests are written in TypeScript.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Notes on generating the initial &lt;code&gt;yarn.lock&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Suppose that you are trying to apply this setup to a new codebase on a machine that has Yarn v1 installed system-wide, as is the norm.&lt;br&gt;
Because the lock file generated and used by Yarn v2 (which lives on the container side) is not compatible with that generated and used by Yarn v1 (which lives on the host side), the Docker build will fail because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;cp yarn.lock [...]&lt;/code&gt; command in the first stage will fail because &lt;code&gt;yarn.lock&lt;/code&gt; does not exist.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;yarn --immutable&lt;/code&gt; command at the very end will also fail because &lt;code&gt;yarn.lock&lt;/code&gt; needs to be modified i.e. created (&lt;code&gt;yarn install&lt;/code&gt; is deliberately run with the &lt;code&gt;--immutable&lt;/code&gt; flag to warn you if the lock file is out of date).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, because the build fails due to a missing v2 lock file, you have no way of creating a working environment in which to invoke Yarn v2 to generate the v2 lock file. This is a chicken and egg problem.&lt;br&gt;
The solution is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;code&gt;Dockerfile&lt;/code&gt;, replace the &lt;code&gt;cp yarn.lock ../manifests/&lt;/code&gt; step in the first build stage with &lt;code&gt;cp yarn.lock ../manifests/ 2&amp;gt;/dev/null || :&lt;/code&gt;.
This allows &lt;code&gt;cp&lt;/code&gt; to not fail in the absence of &lt;code&gt;yarn.lock&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;Dockerfile&lt;/code&gt;, delete &lt;code&gt;yarn --immutable&lt;/code&gt; at the end.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;docker-compose build base&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;docker-compose run --rm -v $(pwd):/srv/app:delegated base yarn&lt;/code&gt;.
This will generate the v2 &lt;code&gt;yarn.lock&lt;/code&gt; in the project directory (along with a &lt;code&gt;.pnp.js&lt;/code&gt; file that is &lt;code&gt;.gitignore&lt;/code&gt;d).&lt;/li&gt;
&lt;li&gt;Remember to revert the above modifications to &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;"Why the odd location &lt;code&gt;srv/app&lt;/code&gt;," you might ask? According to the &lt;a href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch03s17.html" rel="noopener noreferrer"&gt;Filesystem Hierarchy Standard&lt;/a&gt;, "&lt;code&gt;/srv&lt;/code&gt; contains site-specific data which is served by this system," which is a good fit for a web app. I learned this from an &lt;a href="https://jdlm.info/articles/2019/09/06/lessons-building-node-app-docker.html" rel="noopener noreferrer"&gt;article by John Lees-Miller&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>docker</category>
      <category>vscode</category>
      <category>typescript</category>
      <category>yarn</category>
    </item>
    <item>
      <title>Managing dynamic z-index in component-based UI architecture</title>
      <dc:creator>Huy Nguyen</dc:creator>
      <pubDate>Fri, 05 Apr 2019 17:32:27 +0000</pubDate>
      <link>https://dev.to/huy/managing-dynamic-z-index-in-component-based-ui-architecture-369p</link>
      <guid>https://dev.to/huy/managing-dynamic-z-index-in-component-based-ui-architecture-369p</guid>
      <description>&lt;p&gt;This article is a response to &lt;a href="https://www.smashingmagazine.com/2019/04/z-index-component-based-web-application/"&gt;an article&lt;/a&gt; published in Smashing Magazine recently that discusses the difficulties of managing &lt;code&gt;z-index&lt;/code&gt; in a complex UI application.&lt;br&gt;
Practically speaking, &lt;code&gt;z-index&lt;/code&gt; determines the "stacking order" of a DOM element i.e. whether one element appears above another when their spatial extents overlap.&lt;br&gt;
Despite this fairly straightforward functionality, &lt;code&gt;z-index&lt;/code&gt; can be tricky to work with in practice.&lt;/p&gt;

&lt;p&gt;One example that I frequently encounter while developing interactive graphs and charts is that of adjacent interactive DOM elements that have mutually overlapping hover tooltips.&lt;br&gt;
For example, a graph that provides a hover tooltip for every data point can appear next to a legend that also has its own hover tooltip and we want either hover tooltip to be fully visible when they are shown.&lt;/p&gt;

&lt;p&gt;Below is a live demonstration (with the code &lt;a href="https://github.com/huy-nguyen/z-index-management/tree/problem-demo"&gt;here&lt;/a&gt;).&lt;br&gt;
The viewport is divided into 3 adjacent areas, labeled A, B and C and with different &lt;code&gt;background-color&lt;/code&gt;s.&lt;br&gt;
They each should respond to hover interactions by increasing their &lt;code&gt;background-color&lt;/code&gt;'s &lt;code&gt;opacity&lt;/code&gt; and showing a tooltip that points toward their centers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Because I believe that strong ownership my of content will lead to higher quality content, I host all my writings on my website. You can read the rest of this post &lt;a href="https://www.huy.dev/z-index-management-2019-04/"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>zindex</category>
      <category>react</category>
      <category>emotion</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
