Skip to content

ahuglajbclajep/my-react-template

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

61 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

my-react-template

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.

Advanced settings

With transpileOnly option

See also:

webpack.config.js

{
  module: {
    rules: [
      {
        test: /\.[tj]sx?$/,
-       loader: "ts-loader",
+       loader: "ts-loader?transpileOnly",
        exclude: /node_modules/,
      },
    ],
  },
}

package.json

{
  "scripts": {
+   "lint:type": "tsc -p . --noEmit",
  }
}
With tslib

See also:

$ yarn add tslib

tsconfig.json

{
  "compilerOptions": {
    "outDir": "dist", // for allowJs
+   "importHelpers": true,
  }
}
With polyfills via Babel

See also:

$ 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.

webpack.config.js

{
  module: {
    rules: [
      {
        test: /\.[tj]sx?$/,
-       loader: "ts-loader",
+       loader: "babel-loader",
        exclude: /node_modules/,
      },
    ],
  },
}

tsconfig.json

{
  "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 },
    ],
  ],
};

package.json

{
  "scripts": {
+   "lint:type": "tsc",
  },
+ browserslist: "> 0.2%, not dead, not op_mini all, not ie 11"
}
With HMR and Fast Refresh (experimental)

See also:

$ yarn add -D style-loader react-refresh @pmmmwh/react-refresh-webpack-plugin
With prerender-loader

See also:

$ yarn add -D prerender-loader
$ mv src/index.ejs src/index.html

webpack.config.js

    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;

src/index.tsx

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 />);

src/index.html

  <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:

$ 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.

webpack.config.js

+ 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/,
      },
    ],
  },
}

.stylelintrc.js

module.exports = {
  extends: [
    "stylelint-config-standard",
+   "stylelint-config-styled-components",
  ],
  rules: {
+   "declaration-empty-line-before": null,
  },
}

src/index.tsx

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:

$ 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.

webpack.config.js

{
  module: {
    rules: [
      {
        test: /\.[tj]sx?$/,
-       loader: "babel-loader",
+       use: ["babel-loader", `@linaria/webpack-loader?sourceMap=${dev}`],
        exclude: /node_modules/,
      },
    ],
  },
}

.stylelintrc.js

module.exports = {
  rules: {
+   "declaration-empty-line-before": null,
  },
- ignoreFiles: ["node_modules/**", "dist"],
+ ignoreFiles: ["node_modules/**", "dist", ".linaria-cache"],
}

src/index.tsx

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:

$ yarn add -D comlink-loader

webpack.config.js

{
+ 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}!`;
}

src/index.tsx

+ import { greet } from "./worker";

+ (async () => console.log(await greet("dog")))();
With Workbox

See also:

$ yarn add -D workbox-webpack-plugin

webpack.config.js

+ const { GenerateSW } = require("workbox-webpack-plugin");

{
  plugins: [
    new MiniCssExtractPlugin(),
+   new GenerateSW({
+     clientsClaim: true,
+     skipWaiting: true,
+     inlineWorkboxRuntime: true,
+     sourcemap: dev
+   }),
  ],
}

src/index.ejs

<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:

$ yarn add -D husky@4 lint-staged

If you do not like the LICENSE after husky@5, you can also use lefthook instead.

package.json

{
+ "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:

.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

package.json

{
  "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:

$ yarn remove {,@types/}react{,-dom}
$ yarn add preact

tsconfig.json

{
  "compilerOptions": {
    "jsx": "react-jsx", // or "react-jsxdev"
+   "jsxImportSource": "preact",
  }
}

.eslintrc.json

{
- "settings": { "react": { "version": "detect" } },
+ "settings": { "react": { "version": "preact" } },

  "rules": {
    "react/react-in-jsx-scope": "off"
+   "react/no-unknown-property": ["error", { "ignore": ["class"] }]
  }
}

src/index.tsx

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:

$ yarn remove {,@types/}react{,-dom}
$ yarn add preact

webpack.config.js

{
- resolve: { extensions: [".ts", ".tsx", ".js", ".jsx"] },
+ resolve: {
+   extensions: [".ts", ".tsx", ".js", ".jsx"],
+   alias: {
+     react: "preact/compat",
+     "react-dom": "preact/compat",
+   },
+ },
}

tsconfig.json

{
  "compilerOptions": {
    "moduleResolution": "node",
+   "paths": {
+     "react": ["./node_modules/preact/compat"],
+     "react-dom": ["./node_modules/preact/compat"]
+   }
  }
}

.eslintrc.json

{
- "settings": { "react": { "version": "detect" } },
+ "settings": { "react": { "version": "preact" } },
}

src/declares.d.ts

// 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.