Warning
You're browsing the documentation for an old version of Webiny. Consider upgrading your project to Webiny 5.35.x.
What You’ll Learn
  • how to create a new package in a Webiny project
  • how to organize files in a Webiny project

Overview
anchor

We often need to share code between multiple packages in our project.

Every Webiny project consists of packages and project applications, you can learn more about them in our key topics section.
Every Webiny project is organized as a monorepo. In this tutorial, you will learn how to organize and share code between multiple packages in a Webiny project. Let’s get started.

What We'll Build
anchor

In this tutorial, we’ll be building a package that contains a simple React component and see how we can share it between multiple packages. The same principles apply to Node.js packages you would use in your API.

Here is the file structure of the package we’re about to build:

// Some files are omitted for the sake of brevity.

├── apps
│   ├── core
│   ├── api
│   ├── admin
│   ├── theme
│   └── website
├── package.json
├── packages
|   |   // This is our new package
│   └── gretting
│       ├── src
│       │   └── index.tsx
│       ├── .babelrc.js
│       ├── README.md
│       ├── package.json
│       ├── tsconfig.build.json
│       └── tsconfig.json
└── yarn.lock

Prerequisites
anchor

A Webiny Project
anchor

This tutorial assumes you have already created a new Webiny project to work on. We recommend reading our install Webiny tutorial which shows you how to do it.

Create a Package
anchor

In this step, we create a new React package.

The Yarn workspaces aim to make working with monorepos easy. Learn more about workspaces external link here.

The Workspaces List
anchor

Before we continue, let’s quickly cover the workspaces list, located in the package.json file in the project root.

The content of the package looks as shown below:

(...)
"workspaces": {
    "packages": [
      "apps/admin",
      "apps/website",
      "apps/theme",
      "apps/api/graphql",
      "apps/api/headlessCMS"
    ]
},
(...)

As you can see from the example above, you can define exact workspace paths, or provide a wildcard to mark each subfolder as a workspace.

In this tutorial, we use the latter.

Initialize the Package
anchor

Enough with the theory, let’s dive in and initialize the package.

First, create a folder called packages inside the root of your project where we add our custom package.

Packages are just regular NPM packages, or in other words, folders with their own package.json

Let’s create a folder called greeting inside packages. Now that we’ve created our new folder, let’s initialize a new package in it. For that, we need to create a package.json file inside that folder.

You can add it manually or use the following command inside the newly created folder:

yarn init

Once we execute the above command, we will be presented with a couple of questions as shown below:

yarn inityarn init
(click to enlarge)

You can also run yarn init -y to use sensible defaults.

Depending on your input, the generated package.json file’s content may look similar to the following:

{
  "name": "@examples/greeting",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "license": "MIT"
}

The name property defined in the package’s package.json will be used to later import it.

Create the Package Content
anchor

Now that we’ve initialized a new package, let’s start by adding a couple of files.

├── packages
|   |   // This is our new package.
│   └── greeting
│       │ // All the source code for the React component will be in this folder.
│       ├── src
│       │   └── index.tsx
│       ├── // A configuration file for babel. More on that later.
│       ├── .babelrc.js
│       ├── // A text file about the package.
│       ├── README.md
│       ├── // This file holds various metadata relevant to the package.
│       ├── package.json
│       ├── // A configuration file of the TypeScript compiler (tsc) used when package is being built.
│       ├── tsconfig.build.json
│       └── // A configuration file for Typescript compiler used by your IDE.
│       └── tsconfig.json
└── yarn.lock

Source Code
anchor

First, we write the source code for our example React component. For that, create a src folder inside packages/greeting and then add the index.tsx file inside it with the following code:

packages/greeting/src/index.tsx
import React from "react";

const WelcomeMessage = () => {
  return <h1>Welcome to Webiny</h1>;
};

export default WelcomeMessage;

Here we’re creating a very simple React component. But, you can write whatever logic you need for your project.

Now that we have our desired code in place. We can move to the next step which is adding the required configuration files.

To build our package, we need to add the following configuration files:

Let’s create them one by one.

You can check out the full list of tools and libraries included in every Webiny.

package.json
anchor

Let’s start with the package.json file.

First, we need to add the following devDependencies and dependencies as shown below:

packages/greeting/package.json
{
  (...)
  "dependencies": {
    "react": "^16.14.0",
    "react-dom": "^16.14.0"
  },
   "devDependencies": {
    "@babel/cli": "^7.5.5",
    "@babel/core": "^7.5.5",
    "@babel/preset-env": "^7.5.5",
    "@babel/preset-react": "^7.0.0",
    "@babel/preset-typescript": "^7.8.3",
    "@svgr/webpack": "^4.3.2",
    "babel-plugin-named-asset-import": "^1.0.0-next.3e165448",
    "rimraf": "^3.0.2",
    "typescript": "^4.1.3"
  },
  (...)
}

You can find out the full example code used in this tutorial in our repo external link.

Let’s quickly discuss all of them:

dependencies
anchor

This field defines other packages (dependencies) we will use in the code.

In our case we need the following:

  • react external link: React is a JavaScript library for creating user interfaces.
  • react-dom external link: Serves as the entry point to the DOM and server renderers for React.

devDependencies
anchor

This value is used to specify the packages that are only needed for local development and testing.

In our case we need the following:

After that, we add the following scripts inside the package.json file:

scripts
anchor

The “scripts” external link property of your package.json file supports a number of built-in scripts and their preset life cycle events as well as arbitrary scripts.

In our case we need the following:

  • build external link: it removes the content of the dist folder and compiles the source code via babel and runs the postbuild command.
  • watch external link: it runs the babel compiler in watch mode, which means the latest changes will compile automatically as source file content changes.
  • postbuild external link: as the name suggests, it runs after the completion of the build command. We use it to copy the meta files like package.json, README.md into the dist folder and compile typescript code.

After adding script the package.json file look as shown below:

packages/greeting/package.json
{
  (...)
  "dependencies": {
    "react": "^16.14.0",
    "react-dom": "^16.14.0"
  },
   "devDependencies": {
    "@babel/cli": "^7.5.5",
    "@babel/core": "^7.5.5",
    "@babel/preset-env": "^7.5.5",
    "@babel/preset-react": "^7.0.0",
    "@babel/preset-typescript": "^7.8.3",
    "@svgr/webpack": "^4.3.2",
    "babel-plugin-named-asset-import": "^1.0.0-next.3e165448",
    "rimraf": "^3.0.2",
    "typescript": "^4.1.3"
  },
   "scripts": {
    "build": "rimraf ./dist '*.tsbuildinfo' && babel src -d dist --source-maps --copy-files --extensions \".ts,.tsx\" && yarn postbuild",
    "watch": "babel src -d dist --source-maps --copy-files --extensions \".ts,.tsx\" --watch",
    "postbuild": "cp package.json README.md dist/ && tsc -p tsconfig.build.json"
  }
  (...)
}

publishConfig
anchor

And finally, we add publishConfig which is a set of configuration values, usually used for package publishing purposes. But, in our case, this is what enables us to import our newly created package from other packages in different project applications. Webiny uses this to link your package in node_modules with the appropriate target folder, which will be dist once the package is built.

The proper linking of packages is established via the built-in link-workspaces command, defined in your root package.json file.

After adding publishConfig the package.json file look as shown below:

packages/greeting/package.json
{
 (...)
 "dependencies": {
   "react": "^16.14.0",
   "react-dom": "^16.14.0"
 },
  "devDependencies": {
   "@babel/cli": "^7.5.5",
   "@babel/core": "^7.5.5",
   "@babel/preset-env": "^7.5.5",
   "@babel/preset-react": "^7.0.0",
   "@babel/preset-typescript": "^7.8.3",
   "@svgr/webpack": "^4.3.2",
   "babel-plugin-named-asset-import": "^1.0.0-next.3e165448",
   "rimraf": "^3.0.2",
   "typescript": "^4.1.3"
 },
   "publishConfig": {
   "access": "public",
   "directory": "dist"
 },
  "scripts": {
   "build": "rimraf ./dist '*.tsbuildinfo' && babel src -d dist --source-maps --copy-files --extensions \".ts,.tsx\" && yarn postbuild",
   "watch": "babel src -d dist --source-maps --copy-files --extensions \".ts,.tsx\" --watch",
   "postbuild": "cp package.json LICENSE README.md dist/ && tsc -p tsconfig.build.json"
 }
}

Learn more about publishConfig external link.

.babelrc.js
anchor

Now let’s take a look at the .babelrc.js file which is a configuration file for a tool called babel external link.

Babel is a JavaScript compiler. We need it because:

  • we’re writing the React code in JSX syntax which needs to be converted to JS
  • we’re also using the latest JavaScript features and syntax which are not supported in all browsers, and therefore, need to be converted

In the .babelrc.js we just export the .babel.react configuration file which is defined in the project root.

packages/greeting/.babelrc.js
module.exports = require("../../.babel.react")({ path: __dirname });

Every Webiny project comes with a .babel.react.js and .babel.node.js. You don’t need to know all the configurations. But, if you’re interested feel free to check the full configuration file external link.

tsconfig.build.json
anchor

Every Webiny project prioritizes TypeScript external link.

And it needs to be compiled and the tsconfig.build.json file corresponds to the configuration file of the TypeScript compiler (tsc) used when package is being built.

Webiny uses TypeScript (v4). Only in a few cases, like for example configuration files, you will encounter pure JavaScript.

Let’s take a look at the content of this file:

packages/greeting/tsconfig.build.json
{
  "extends": "../../tsconfig.build.json",
  "include": ["./src"],
  "exclude": ["node_modules"],
  "compilerOptions": {
    "rootDir": "./src",
    "outDir": "./dist",
    "declarationDir": "./dist"
  }
}

The tsconfig.build.json file specifies the root files and the compiler options required to compile the project. Please check out the official docs to learn more external link about it.

tsconfig.json
anchor

This is a configuration file for Typescript compiler used by your IDE.

Let’s take a look at the content of this file:

packages/greeting/tsconfig.json
{
  "extends": "../../tsconfig"
}

Like the previous file, we’re just using the configuration defined in the project root external link here.

README.md (Optional)
anchor

A README is a text file that introduces and explains a project. It contains information that is commonly required to understand what the project is about.

Preparing the Package for Usage
anchor

Now that we’ve created our package and added the required configuration files, it is time to use it. To do that we need to take the following steps:

  • install the package
  • build the package

Install the Package
anchor

Now that we have all the files in place, it is time to install and link the package. Run the following command from the root of your project:

yarn install

Running this command will do two things:

  1. install the package dependencies.
  2. link the package.

The link step is performed by the link-workspaces external link script which runs via the postinstall external link hook.

Build the Package
anchor

To use the package we need to build it first.

“And how do we do that?” you may ask, remember we added the build command under the scripts key inside the package.json file of the package. Now it’s time we use it.

We can simply cd into the package folder which is packages/greeting and run:

yarn build

And it will work just fine. But, as your project grows and you add more packages, it becomes a chore to run the same command across multiple packages.

Webiny CLI provides the workspaces run (or ws run for short) command that enables you to run a single command across multiple workspaces at once. The common use case where this might be needed is local development, where you want to watch for code changes on multiple packages, and rebuild them accordingly.

For example, to establish a watch session across multiple packages, located in a specific folder, you can run the following command:

yarn webiny ws run watch --folder packages

On the other hand, if you wanted to build all of the packages, again, located in a specific folder, you can run:

yarn webiny ws run build --folder packages

The ws run command executes the command in question for every workspace present in the folder. In our case, packages/greeting.

Using the Package in Apps
anchor

After completing all these steps you can now simply import and use it as a regular npm package. You can import and use this newly created package in any application or any other package inside the same Webiny project.

import WelcomeMessage from "@examples/greeting";

Conclusion
anchor

Congratulations!

You’ve successfully created a new package in a Webiny project. Monorepo organization makes it possible to structure different logical pieces of your project as multiple packages.

You can also check out a similar code example in our repo external link. If you have further questions, feel free to ask external link for additional help.