Lets do a proper setup for a TypeScript project for the last time. This repo will serve as a boilerplate for every future ts project to come.
The most relevant decision making will be captured for once and for all.
(This shows an example for a npm browser project, but will be fairly similair for NodeJS or Electron projects)
Source code is placed under the src
directory.
Build files are placed under the build
directory.
Distributions are placed under the dist
directory.
(e.g. in case of electron).
The root folder structure should be something along the lines of:
/
.
..
src
dist
build
package.json
Projects that needs to be published should have a namespace in the name: @kn/<project_name>
For the scripts we follow the refspec format: <+><source>:<destination>
Source being the bigger entity and destination being the smaller entity.
e.g.
build:cjs
deploy:production
When building npm packages a dual commonJS/ESM packages
"type": "module",
"main": "dist/index.js"
Can be replaced for:
"exports": {
"import": "./dist/mjs/index.js",
"require": "./dist/cjs/index.js"
}
Additionally specify a files
property in package.json
to indicate which files should end up
in the distribution.
Setup commitlint with conventional commits and Husky
Essentially this boils down to
npm install --save-dev @commitlint/cli
npm install --save-dev @commitlint/config-conventional
echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
npm install husky --save-dev
npx husky-init && npm install
rm .husky/pre-commit
Create the .husky/commit-msg file
cat <<EEE > .husky/commit-msg
#!/bin/sh
. "\$(dirname "\$0")/_/husky.sh"
npx --no -- commitlint --edit "\${1}"
EEE
Conventional commits go hand-in-hand with semantic versioning.
Editorconfig to atleast enforce consistent behaviour across different editors.
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
max_line_length = 100
Keep the distinction between eslint and prettier clear. Prettier will take care of ALL style formatting rules and eslint should take care of ALL code quality rules.
npm install eslint --save-dev
Since we're using both prettier and eslint there isn't really a good out-of-the-box style convention.
Running the command below should generate a nice .eslintrc
file
npm init @eslint/config
Disable all style related rules to not conflict with prettier
npm install --save-dev eslint-config-prettier
Update eslintrc.js
{
"extends": ["whatever-more-configs-are-here", "prettier"]
}
eslint:recommended
and plugin:@typescript-eslint/recommended
contain most of the code-quality
linting rules.
npm install --save-dev --save-exact prettier
echo "package-lock.json" > .prettierignore
touch .prettierrc.js
Prettier will respect the .editorconfig settings
Following the philosophy of prettier we DON'T use eslint-plugin-prettier
which would use prettier
as if it was a linter. Rather we only enable auto-format on save powered by our editor.
The idea is that you as a developer are never bothered by styling issues, because they shouldn't consume any second of your time.
Additionally add a lint-staged husky hook:
npm install --save-dev lint-staged
Add .lintstagedrc
with:
{
"src/**/*.ts": "eslint",
"**/*": "prettier --write --ignore-unknown"
}
Optionally add lint-staged to the pre-commit hook to ensure that no unlinted code ends up in the repo. This however is quite aggressive.
npx husky add .husky/pre-commit "npx lint-staged"
Install prettier plugin Press CMD+P and run
ext install esbenp.prettier-vscode
Setup some basics
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.rulers": [100]
}
npm install typescript --save-dev
Get a default tsconfig.json
file
npx tsc --init
Example:
{
"compilerOptions": {
"target": "es2021",
"module": "es2022",
"rootDir": "./src",
"outDir": "./build",
"forceConsistentCasingInFileNames": true,
"strict": true
},
"include": ["./src/**/*.ts"]
}
To create npm packages that you'd like to use both with commonJS and ESM, a dual built setup can be achieved by splitting-up tsconfig into the 'bare' config and the output format.
Optionally turn off fast-forwarding on merge
git config merge.ff no
Before publishing to npmjs.com create an NPM_TOKEN
npm adduser
Option 1: Releasing npm packages using standard-version
in combination with conventional commits
to take care of our semantic versioning.
npm install --save-dev standard-version
npm set-script release "standard-version"
Now to create a release simply do
npm run release
To publish the current release
npm publish
The version number will automatically be updated according to the conventional commit messages.
Semantic release is fully automated and pushes your releases from a ci environment
Create a ci workflow file as in .github/workflows/release.yml
Create release.config.js
to also update package.json on every new release
module.exports = {
branches: ["main"],
plugins: [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/npm",
"@semantic-release/github",
[
"@semantic-release/git",
{
assets: ["package.json"],
message: "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}",
},
],
],
};
For the above to work, HUSKY would need to be disabled since the ${nextRelease.notes} doesn't fit the conventional commit guidelines as to how lengthy the commit body can be.
After pushing and github has ran its pipeline, you'd need to pull to get the updated package.json version number locally.
To setup (automatically), which will create an npm_token and set it as a repository secret.
npx semantic-release-cli setup
npm install --save-dev semantic-release
Set the NPM_TOKEN
as a github secret.
Additionally ensure that Settings
> Actions
> General
> Workflow permissions
is set to Read and write permissions
.
The github-actions bot will try to write to the repositry. Failing to setup correctly will result in a permission denied to github-actions[bot]
error