I decided to start my new project with the development of a template for GitHub repositories, and in this article, I want to share my experience!
By the way, the whole process that will be discussed later I recorded on video (with all my errors along the way π) take a look, suddenly you will like it π
Why do I need a template for the GitHub repository?
A template is a very convenient tool if you often start developing new projects and you need a preconfigured starting template with installed dependencies, structure and customized automation processes
Step by step
Create a new GitHub repository. At this stage, this repository is no different from your regular repositories
Go to your local folder where your project will be located. Create a new git repository (you can immediately execute the commands that GitHub offers after creating the remote repository) and create the package.json
file
git init
npm init
npm will ask you some questions and based on your answers will create a basic package.json
, but since this file is very important for us, let's take a closer look
name
This is the identifier of your package, which must be unique. Advice from me, check in advance if the name is free at npmjs.com if you are going to publish your package there
version
Shows the current version of the application
description
A brief description of your project
main
The main entry point to your project. This field (and the next two) should indicate the place where your package will be collected (I usually use the dist
folder)
modules
Pointer to an ECMAScript Module
types
Type Π² declaration pointer for TS
files
The list of files that will be included in the build after your package is installed as a dependency. I recommend placing only the files necessary for your package to work, it makes no sense to install all the files that you use during the development process (here I just specify the dist
folder)
repository
It's important to specify the place where your code is stored for the convenience of contributors (just copy the link to your GitHub repository here)
author
Just indicate yourself or your team
license
Indicate how other users can use your package. This information also appears in your package when published to npm and to GitHub. GitHub also recommends adding the LICENSE.md
file to expand the license. In my case, I choose MIT
keywords
List of keywords by which other people can find your package
bugs
Link to where users report problems in your project. In my case, this is a link to GitHub issues
As a result, I got such a package.json
file:
{
"name": "como-north",
"version": "1.0.0",
"description": "GitHub template for starting new projects",
"main": "./dist/index.js",
"module": "./dist/index.es.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/Alexandrshy/como-north"
},
"author": "Alex Shualev <alexandrshy@gmail.com>",
"license": "MIT",
"keywords": [
"template",
"javascript",
"package"
],
"bugs": {
"url": "https://github.com/Alexandrshy/como-north/issues"
},
"homepage": "https://github.com/Alexandrshy/como-north",
}
Do not forget to make commits, if you have already watched my video, I do it constantly π
Now let's move on to the tools. I will not dwell on individual libraries for a long time or explain my choice, each tool is variable and can be replaced or completely removed, I just tell you one of the options that suit me
Linters
In my template, I'll use a bunch of ESLint and Prettier. In my opinion, this is the best choice at the moment because of the flexibility of the settings
π¦ Prettier
Prettier is a code formatting tool that aims to use predefined rules for the code design. It formats the code automatically and has extensions for modern IDE
Install the package:
npm i prettier -D
Write the configuration:
{
"singleQuote": true,
"parser": "typescript",
"tabWidth": 4,
"bracketSpacing": false,
"printWidth": 100,
"trailingComma": "all"
}
You can also create .prettierignore
if you have files that you do not want to format
.github/
.idea/
node_modules/
dist/
π¦ ESLint
This tool analyzes the code to help detect problematic patterns that don't comply with the rules and standards. It works for most programming languages and has a large number of ready-made configurations from large companies and extensions for various tasks
Install the package:
npm i eslint eslint-config-prettier eslint-plugin-prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-jsdoc -D
- eslint-config-prettier - is a config that disables rules that conflict with Prettier
-
eslint-plugin-prettier - exposes a "recommended" configuration that configures both
eslint-plugin-prettier
andeslint-config-prettier
in a single step -
@typescript-eslint/eslint-plugin - an ESLint-specific plugin which, when used in conjunction with
@typescript-eslint/parser
, allows for TypeScript-specific linting rules to run -
@typescript-eslint/parser - an ESLint-specific parser which leverages
typescript-estree
and is designed to be used as a replacement for ESLint's default parser, espree - eslint-plugin-jsdoc - JSDoc linting rules for ESLint
All additional packages are optional and depend on your goals. You can also pay attention to eslint-config-airbnb this package provides the developer with the configuration from Airbnb
module.exports = {
plugins: ['@typescript-eslint', 'prettier', 'jsdoc'],
extends: [
'plugin:@typescript-eslint/recommended',
'prettier/@typescript-eslint',
'plugin:prettier/recommended',
'plugin:jsdoc/recommended',
],
rules: {},
overrides: [
{
files: ['src/*/*'],
rules: {
'max-lines': 'off',
'max-nested-callbacks': 'off',
'max-statements': 'off',
},
},
],
settings: {
node: {
extensions: ['.ts', '.json'],
},
},
};
Add scripts for linter:
"prettier": "prettier '**/*.{js,ts}' --ignore-path ./.prettierignore",
"lint": "eslint '*/**/*.{js,ts}'",
π¦ TypeScript
JavaScript is a dynamically typed language, meaning the compiler doesn't know what type of variable you are using until the variable is initialized. Such things can cause difficulties and errors in your projects. However, TypeScript helps solve these problems. I use TypeScript in all my projects regardless of their size. I believe that early catching errors is very important and it is better to foresee the structure of your functions in advance than spending time catching bugs later
npm i typescript -D
The tsconfig.json
file specifies the root files and the compiler options required to compile the project.
{
"compilerOptions": {
"outDir": "dist",
"module": "es2015",
"target": "es6",
"lib": [
"es5",
"es6",
"es7",
"es2017",
"dom"
],
"sourceMap": true,
"moduleResolution": "node",
"baseUrl": "src",
"skipLibCheck": true,
"strict": true,
"declaration": true
},
"include": [
"src",
"typeScript"
],
"exclude": [
"node_modules",
]
}
You can find all available options here
Add some more scripts:
"types": "tsc --noEmit",
"finish": "npm run lint && npm run types"
The finish
script we need when working on workflows
Now we can create the src/index.ts
export const union = (a: Array<string>, b: Array<string>): Array<string> => [...a, ...b];
And now we can run finish
script
npm run finish
If everything is done correctly we will not get any error
π¦ Babel
We'll add Babel to the template for the correct operation of our code in older versions of browsers
npm i @babel/core @babel/preset-env @babel/preset-typescript -D
Add configuration file
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}, modules: false, loose: true}],
'@babel/preset-typescript',
],
};
Need to pay attention to
targets
Describes the environments you support/target for your project. You need to specify a minimum environment required for your users
modules
Enable transformation of ES6 module syntax to another module type
loose
Enable "loose" transformations for any plugins in this preset that allow them
We will not create a separate script for running babel since we'll use babel through the plugin in rollup
π¦ Rollup.js
Rollup is a module bundler for JavaScript. Now the Rollup community is very active, and I often see new projects that use Rollup for building. Its main advantage is its easy configuration. Let's add Rollup to the project and write a config file
npm i rollup rollup-plugin-terser rollup-plugin-typescript2 @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve -D
As you can see, in addition to the main package, we install many extensions, let's say a few words about each:
- rollup-plugin-terser - plugin to minimize the generated package
- rollup-plugin-typescript2 - plugin for typescript with compiler errors
- @rollup/plugin-babel - plugin for seamless integration between Rollup and Babel
- @rollup/plugin-commonjs - plugin to convert CommonJS modules to ES6, so they can be included in a Rollup bundle
-
@rollup/plugin-node-resolve - plugin which locates modules using the Node resolution algorithm, for using third party modules in
node_modules
And now the config file itself
import typescript from 'rollup-plugin-typescript2';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import {terser} from 'rollup-plugin-terser';
import pkg from './package.json';
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
export default {
input: 'src/index.ts',
output: [
{
file: pkg.main,
format: 'umd',
name: 'ComoNorth',
},
{
file: pkg.module,
format: 'es',
},
],
plugins: [
typescript({
rollupCommonJSResolveHack: true,
clean: true,
}),
babel({
exclude: 'node_modules/**',
extensions,
}),
resolve(),
commonjs(),
terser(),
],
};
Add new scripts:
"build": "npm run build:clean && npm run build:lib",
"build:clean": "rimraf dist",
"build:lib": "rollup -c",
To understand that we did everything right, let's run the script. As a result, we should not see any errors in the console and a new dist folder should appear in the project
npm run build
π₯ Automation
In your project, you should think not only about the dev build, but also the delivery processes of your package to your users. Each of your changes should be reflected in a file with changes so that other people can follow the development process, your project must be correctly versioned according to your changes and published immediately (in my case in npm). Let's take it in order
Checking the commit message
Since we want to record all changes made to our package, we need to structure our commit messages. For this, we'll use commitlint
npm i @commitlint/cli @commitlint/config-conventional husky -D
Configuration file
{
"parserPreset": "conventional-changelog-conventionalcommits",
"rules": {
"body-leading-blank": [
1,
"always"
],
"footer-leading-blank": [
1,
"always"
],
"header-max-length": [
2,
"always",
150
],
"scope-case": [
2,
"always",
"lower-case"
],
"subject-case": [
2,
"never",
[
"sentence-case",
"start-case",
"pascal-case",
"upper-case"
]
],
"subject-empty": [
2,
"never"
],
"subject-full-stop": [
2,
"never",
"."
],
"type-case": [
2,
"always",
"lower-case"
],
"type-empty": [
2,
"never"
],
"type-enum": [
2,
"always",
[
"chore",
"ci",
"docs",
"feat",
"fix",
"refactor",
"revert",
"style",
"test"
]
]
}
}
As you may have noticed we also installed the husky package as a dependency. This package is very well described on their page on GitHub: "Husky can prevent bad git commit
, git push
and more". Required for correct operation husky
: node
>= 10 and git
>= 2.13.0
Add the following code to package.json
:
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"src/**/*.{js,ts}": [
"npm run lint"
]
},
Now, before each attempt to make a git commit
, we will run the lint
script, and each commit message we will check for compliance with the template. Experiment time, try the following code:
git add .
git commit -m "added commitlint and husky"
And we get an error, but that is what we were waiting for! This means that we cannot make commits with arbitrary commits
git add .
git commit -m "feat: added commitlint and husky"
But this will work. The feat
tag that we used in the commit message is necessary for further versioning our package
GitHub workflows
You can create custom workflows to automate your project's software development life cycle processes. Detailed workflow instructions.
The first process that we will set up is the process of working with pull requests. Typically, this process involves building the project, checking linter, running tests, and so on
First, create a file .github/workflows/pull-requests_check.yml
And add the following
name: Pull-Requests Check
on: [pull_request]
jobs:
Test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
- name: Finish
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
npm i
npm run finish
After these files get into your GitHub repository and you create a new pull request in the Actions
tab, you'll see a window with the result of your process. If everything is green, excellent, you can merge your request!
It took me 20 seconds to complete my process, but it all depends on the complexity of your workflow, if you run a large number of tests for your project, it may take several minutes
Now let's create a more complex workflow for automatically publishing the package in npm and recording changes to the new version of the package in CHANGELOG.md
name: Release
on:
push:
branches:
- master
jobs:
Release:
name: release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
always-auth: true
node-version: 12
registry-url: "https://npm.pkg.github.com"
scope: "@Alexandrshy"
- name: Install dependencies
run: npm i
- name: Build
run: npm run build
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v2
id: semantic
with:
branch: master
extra_plugins: |
@semantic-release/git
@semantic-release/changelog
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Push updates to branch for major version
if: steps.semantic.outputs.new_release_published == 'true'
run: git push https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git HEAD:refs/heads/v${{steps.semantic.outputs.new_release_major_version}}
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
Here you should pay attention to the following two things
- We used the GitHub Actions cycjimmy/semantic-release-action@v2 which in turn is a wrapper over semantic-release. GitHub Actions has a lot of useful tools for automating various processes, just check out the market place and you will be surprised π
-
secrets.GH_TOKEN
andsecrets.NPM_TOKEN
GitHub provides a token that you can use to authenticate on behalf of GitHub Actions. These tokens must be generated (for npm and for GitHub) and added to your repository (for example https://github.com/{your-name}/{repository-name}/settings/secrets)
If you didn't make any mistakes, you'll get your package published in npm
Now every change that gets into the master branch will start this process and create a new version depending on the tags that you added to your commit message. For example, if you had version 1.0.0 of the package and you made a merge with the commit message: "fix: eslint config" after the workflow is complete, you'll receive a new version of package 1.0.1
Dependency management
To control dependencies, I recommend that you add dependentabot. This bot automatically checks your dependencies and the need to update them
On the site you need to sign in via GitHub. Then give access to those repositories that dependabot should monitor
And in the project itself you need to create a .dependabot/config.yml
with this content:
version: 1
update_configs:
- package_manager: "javascript"
directory: "/"
update_schedule: "weekly"
target_branch: "master"
commit_message:
prefix: "fix"
target_branch: "dependa"
default_reviewers:
- Alexandrshy
You can configure the auto-merge immediately in the master, but I would not recommend doing this, I decided to put all the updates in a separate branch and then update the master yourself using a single pull request
Minor improvements
All we have to do is add README.md
and LICENSE
README.md
is your space for creativity, but don't forget, its main purpose is to show you how to work with your package very briefly. You can create a LICENSE
via the GitHub interface. This is convenient, since GitHub has pre-prepared templates.
To do this, click on the button "Create new file". Enter the file name LICENSE
, then click on the button "Choose a license template" and select the license that suits you
Well and most importantly, make the resulting project a template for your future work. To do this, we go to the repository settings and click on the "Template repository" checkbox, that's all!
Work result
Como-north my template that I'll use in my next projects and maybe I'll update it as needed
Video
Top comments (0)