My simple React + TypeScript template for VS Code.
vscode-eslint, vscode-stylelint and prettier-vscode are required.
$ yarn install
$ code . & yarn start
If you are running in cmd or powershell, use cross-env for NODE_ENV
.
With transpileOnly option
See also:
{
module: {
rules: [
{
test: /\.[tj]sx?$/,
- loader: "ts-loader",
+ loader: "ts-loader?transpileOnly",
exclude: /node_modules/,
},
],
},
}
{
"scripts": {
+ "lint:type": "tsc -p . --noEmit",
}
}
With tslib
See also:
- https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#support-for-external-helpers-library-tslib
- https://github.com/microsoft/tslib/tree/2.0.3#usage
$ yarn add tslib
{
"compilerOptions": {
"outDir": "dist", // for allowJs
+ "importHelpers": true,
}
}
With polyfills via Babel
See also:
- https://github.com/babel/babel-loader/tree/v8.2.1#usage
- https://babeljs.io/docs/en/babel-plugin-transform-typescript#caveats
- https://babeljs.io/docs/en/plugins/#plugin-ordering
- https://babeljs.io/docs/en/babel-preset-env#bugfixes
- https://github.com/zloirock/core-js/tree/v3.7.0#babelpreset-env
- babel/babel#10008
- https://github.com/babel/babel-loader/tree/v8.2.1#babel-is-injecting-helpers-into-each-file-and-bloating-my-code
- https://github.com/browserslist/browserslist/tree/4.14.7#configuring-for-different-environments
- https://create-react-app.dev/docs/supported-browsers-features/#configuring-supported-browsers
- https://github.com/Microsoft/TypeScript-Babel-Starter
- babel/babel#8121
$ yarn remove ts-loader
$ yarn add -D babel-loader @babel/core @babel/preset-{typescript,react,env} @babel/plugin-transform-runtime
$ yarn add core-js @babel/runtime
The @babel/preset-typescript
is not enough to convert all TypeScript syntaxes.
If you want to use the enum
syntax or stage 3 syntaxes, please set up additional plugins like babel-plugin-const-enum.
If @babel/preset-env
generates code that depends on regenerator-runtime
, you can suppress this with babel-plugin-transform-async-to-promises, which is also used in microbundle.
{
module: {
rules: [
{
test: /\.[tj]sx?$/,
- loader: "ts-loader",
+ loader: "babel-loader",
exclude: /node_modules/,
},
],
},
}
{
"compilerOptions": {
- "target": "es6",
+ "target": "esnext",
"outDir": "dist", // for allowJs
+ "noEmit": true,
+ "isolatedModules": true,
}
}
babel.config.js
module.exports = {
presets: [
[
"@babel/env",
{
useBuiltIns: "usage",
bugfixes: true,
corejs: require("core-js/package.json").version,
},
],
["@babel/preset-react", { runtime: "automatic" }],
"@babel/typescript",
],
plugins: [
[
"@babel/transform-runtime",
{ version: require("@babel/runtime/package.json").version },
],
],
};
{
"scripts": {
+ "lint:type": "tsc",
},
+ browserslist: "> 0.2%, not dead, not op_mini all, not ie 11"
}
With HMR and Fast Refresh (experimental)
See also:
- "With polyfills via Babel" section in this README
- https://webpack.js.org/guides/hot-module-replacement
- https://github.com/facebook/create-react-app/blob/v4.0.0/packages/react-scripts/config/webpack.config.js
- facebook/react#16604
- https://github.com/pmmmwh/react-refresh-webpack-plugin
$ yarn add -D style-loader react-refresh @pmmmwh/react-refresh-webpack-plugin
With prerender-loader
See also:
- https://v4.webpack.js.org/concepts/loaders/#inline
- GoogleChromeLabs/prerender-loader#7
- GoogleChromeLabs/prerender-loader#40
- GoogleChromeLabs/prerender-loader#6
$ yarn add -D prerender-loader
$ mv src/index.ejs src/index.html
plugins: [
new HtmlWebpackPlugin({
- title: require("./package.json").name,
+ template: "!!prerender-loader?string!./src/index.html",
scriptLoading: "defer",
}),
]
src/App.tsx
import { useCallback, useState } from "react";
const App: React.FC = () => {
const [count, setCount] = useState(0);
const onclick = useCallback(() => setCount((c) => c + 1), []);
return (
<>
<h1>{count}</h1>
<button onClick={onclick}>add</button>
</>
);
};
export default App;
import { hydrate } from "react-dom";
import App from "./App";
import "./style.css";
hydrate(<App />, document.getElementById("root"));
src/entry.tsx
import { renderToString } from "react-dom/server";
import App from "./App";
export default (): string => renderToString(<App />);
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
- <div id="root"></div>
+ <div id="root">{{prerender:./src/entry.tsx}}</div>
</body>
With styled-components
See also:
- https://styled-components.com/docs/api#typescript
- https://styled-components.com/docs/tooling#typescript-plugin
- https://github.com/Igorbek/typescript-plugin-styled-components/tree/1.4.4#ts-loader
- https://styled-components.com/docs/tooling#stylelint
- stylelint/stylelint#4481
- styled-components/stylelint-processor-styled-components#278
$ yarn add styled-components
$ yarn add -D @types/styled-components typescript-plugin-styled-components stylelint-config-styled-components
Since styled-components uses stylis, there is no need to configure sass-loader, Autoprefixer and CSS Modules (css-loader?modules
).
If you do not import CSS files, you do not need css-loader
, mini-css-extract-plugin
and optimize-css-assets-webpack-plugin
.
+ const scTransformer = require("typescript-plugin-styled-components").default;
{
module: {
rules: [
{
test: /\.[tj]sx?$/,
loader: "ts-loader",
+ options: {
+ getCustomTransformers: () => ({
+ before: [scTransformer({ minify: true })],
+ }),
+ },
exclude: /node_modules/,
},
],
},
}
module.exports = {
extends: [
"stylelint-config-standard",
+ "stylelint-config-styled-components",
],
rules: {
+ "declaration-empty-line-before": null,
},
}
import ReactDOM from "react-dom";
import styled from "styled-components";
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
`;
ReactDOM.render(<Title>Hello, React!</Title>, document.getElementById("root"));
With linaria
See also:
- "With polyfills via Babel" section in this README
- https://github.com/callstack/linaria/blob/v2.0.2/docs/BUNDLERS_INTEGRATION.md
- https://github.com/callstack/linaria/blob/v2.0.2/docs/LINTING.md
- callstack/linaria#614
$ yarn add linaria
$ echo '.linaria-cache' >> .gitignore
Since linaria uses stylis (as well as styled-components), there is no need to configure sass-loader, Autoprefixer and CSS Modules.
{
module: {
rules: [
{
test: /\.[tj]sx?$/,
- loader: "babel-loader",
+ use: ["babel-loader", `@linaria/webpack-loader?sourceMap=${dev}`],
exclude: /node_modules/,
},
],
},
}
module.exports = {
rules: {
+ "declaration-empty-line-before": null,
},
- ignoreFiles: ["node_modules/**", "dist"],
+ ignoreFiles: ["node_modules/**", "dist", ".linaria-cache"],
}
import { styled } from "linaria/react";
import ReactDOM from "react-dom";
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
`;
ReactDOM.render(<Title>Hello, React!</Title>, document.getElementById("root"));
With comlink-loader
See also:
- https://github.com/GoogleChromeLabs/comlink-loader/tree/2.0.0#singleton-mode
- GoogleChromeLabs/comlink-loader#1
- webpack-contrib/worker-loader#142
- https://github.com/GoogleChromeLabs/comlink-loader/blob/2.0.0/src/index.js#L38
$ yarn add -D comlink-loader
{
+ output: { globalObject: "self" },
module: {
rules: [
+ {
+ test: /\.?worker\.[tj]s$/,
+ loader: "comlink-loader?singleton&name=[name].js",
+ },
{
test: /\.[tj]sx?$/,
loader: "ts-loader",
exclude: /node_modules/,
},
],
},
}
src/worker.ts
/* eslint-disable @typescript-eslint/require-await */
export async function greet(subject: string): Promise<string> {
return `Hello, ${subject}!`;
}
+ import { greet } from "./worker";
+ (async () => console.log(await greet("dog")))();
With Workbox
See also:
- https://developers.google.com/web/tools/workbox/guides/generate-service-worker/webpack
- https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-webpack-plugin.GenerateSW
- GoogleChrome/workbox#2064
- GoogleChrome/workbox#2493
$ yarn add -D workbox-webpack-plugin
+ const { GenerateSW } = require("workbox-webpack-plugin");
{
plugins: [
new MiniCssExtractPlugin(),
+ new GenerateSW({
+ clientsClaim: true,
+ skipWaiting: true,
+ inlineWorkboxRuntime: true,
+ sourcemap: dev
+ }),
],
}
<html>
<body>
<div id="root"></div>
+ <!-- prettier-ignore -->
+ <script>
+ addEventListener("load",_=>navigator.serviceWorker.register("./service-worker.js"))
+ </script>
</body>
</html>
With pre-commit hook
See also:
- https://github.com/typicode/husky/tree/v4.3.0#install
- https://github.com/typicode/husky/blob/v5.0.4/LICENSE
- https://github.com/okonet/lint-staged/tree/v10.5.1#running-multiple-commands-in-a-sequence
$ yarn add -D husky@4 lint-staged
If you do not like the LICENSE after husky@5
, you can also use lefthook instead.
{
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ },
+ "lint-staged": {
+ "src/**": "stylelint --fix",
+ "src/**/*.[tj]s{,x}": "eslint --fix",
+ "*": "prettier -wu"
+ }
}
If the outputs conflict, you can run tasks serially with lint-staged -p false
.
With GitHub Actions and Renovate
See also:
- https://docs.github.com/en/free-pro-team@latest/actions/guides/building-and-testing-nodejs
- https://github.community/t/github-actions-branch-conditional/16057
- https://docs.renovatebot.com/install-github-app/
- https://github.com/ahuglajbclajep/renovate-config#usage
- renovatebot/renovate#5411
.github/workflows/main.yml
name: main
on: push
jobs:
npm-script:
strategy:
fail-fast: false
matrix:
script: [build, "lint:format", "lint:ts", "lint:css"]
if: "!contains(github.event.head_commit.message, '[ci skip]')"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14
- uses: actions/cache@v2
with:
path: ~/.cache/yarn
key: yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: yarn-
- run: yarn install --frozen-lockfile
- run: yarn ${{ matrix.script }}
- if: >
matrix.script == 'build' &&
github.event_name == 'push' &&
github.ref == 'refs/heads/master' &&
github.event.head_commit.author.name != 'renovate[bot]' &&
!contains(github.event.head_commit.message, '[deploy skip]')
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
If you want to use the npm
, change it as follows:
- uses: actions/cache@v2
with:
- path: ~/.cache/yarn
- key: yarn-${{ hashFiles('**/yarn.lock') }}
- restore-keys: yarn-
- - run: yarn install --frozen-lockfile
- - run: yarn ${{ matrix.script }}
+ path: ~/.npm
+ key: npm-${{ hashFiles('**/package-lock.json') }}
+ restore-keys: npm-
+ - run: npm ci
+ - run: npm run ${{ matrix.script }}
.github/renovate.json
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>ahuglajbclajep/renovate-config"]
}
With gh-pages
$ yarn add -D gh-pages
{
"scripts": {
- "build": "NODE_ENV=production webpack -p",
+ "build": "rm -rf && NODE_ENV=production webpack -p",
+ "deploy": "npm run build && gh-pages -d dist",
}
}
You need to use rimraf instead of rm -rf
to run in cmd, and you also need to use run-s instead of &&
to run in powershell (before 7).
With Preact
See also:
- https://preactjs.com/guide/v10/typescript
- https://github.com/preactjs/preact/releases/tag/10.5.0
- jsx-eslint/eslint-plugin-react#1955
- https://github.com/preactjs/preact-cli/blob/v3.0.3/.eslintrc#L20
- https://preactjs.com/guide/v10/differences-to-react/#raw-html-attributeproperty-names
$ yarn remove {,@types/}react{,-dom}
$ yarn add preact
{
"compilerOptions": {
"jsx": "react-jsx", // or "react-jsxdev"
+ "jsxImportSource": "preact",
}
}
{
- "settings": { "react": { "version": "detect" } },
+ "settings": { "react": { "version": "preact" } },
"rules": {
"react/react-in-jsx-scope": "off"
+ "react/no-unknown-property": ["error", { "ignore": ["class"] }]
}
}
import { render } from "preact";
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
render(<h1>Hello, Preact!</h1>, document.getElementById("root")!);
Switching to Preact
See also:
- https://preactjs.com/guide/v10/getting-started#aliasing-react-to-preact
- https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
- preactjs/preact#2150
$ yarn remove {,@types/}react{,-dom}
$ yarn add preact
{
- resolve: { extensions: [".ts", ".tsx", ".js", ".jsx"] },
+ resolve: {
+ extensions: [".ts", ".tsx", ".js", ".jsx"],
+ alias: {
+ react: "preact/compat",
+ "react-dom": "preact/compat",
+ },
+ },
}
{
"compilerOptions": {
"moduleResolution": "node",
+ "paths": {
+ "react": ["./node_modules/preact/compat"],
+ "react-dom": ["./node_modules/preact/compat"]
+ }
}
}
{
- "settings": { "react": { "version": "detect" } },
+ "settings": { "react": { "version": "preact" } },
}
// define the missing types yourself
declare namespace React {
type ChangeEvent<T extends EventTarget> = JSX.TargetedEvent<T>;
}
Type definitions with type
can not be overridden, so type annotations must be added for things like e.target
.