前提条件
予めnode.jsをインストールしておいてください。

npmの初期化とモジュールのインストール

適当なプロジェクトディレクトリを作ったらnpm initpackage.jsonを作ります。

$ npm init -y

次にWebpack関係のモジュールからインストール

$ npm i -D webpack webpack-cli typescript ts-loader

React関係をインストール

$ npm i -S react react-dom

TypeScriptを使用するので定義ファイルもインストールします。

$ npm i -D @types/react @types/react-dom

package.jsonの確認と編集

2020年8月現在インストールされるバージョンは次のようになります。
また、scripts部分に追加して、webpackを実行できるようにします。

package.json

{
"scripts": {
	"build": "webpack --mode=production",
    "start": "webpack -w --mode=development"
},
"devDependencies": {
	"ts-loader": "^8.0.2",
	"typescript": "^4.0.2",
	"webpack": "^4.44.1",
	"webpack-cli": "^3.3.12"
},
"dependencies": {
	"@types/react": "^16.9.46",
	"@types/react-dom": "^16.9.8",
	"react": "^16.13.1",
	"react-dom": "^16.13.1"
},
"private": true
}

TypeScriptの設定ファイル

TypeScriptの設定を記述するファイルを作成します。

$ touch tsconfig.json

中は次のようにします。必要に応じて追記してください。

tsconfig.json

{
	"compilerOptions": {
		"sourceMap": true,
		"target": "es5",
		"module": "es2015",
		"strict": true,
		"strictNullChecks": true,
		"noUnusedLocals": true,
		"noUnusedParameters": false,
		"noImplicitReturns": true,
		"moduleResolution": "node",
		"esModuleInterop": true,
		"allowSyntheticDefaultImports": true,
		"jsx": "react",
		"lib": ["es2020", "dom"]
	}
}

Webpackの設定ファイル

次はWebbpackの設定ファイルを作成します。

$ touch webpack.config.js

今回は編集ファイルをpublic_html/assets/srcディレクトリにいれて、書き出したディレクトリはpublic_html/assets/distに設定します。
プロジェクトのディレクトリ構成によって変更してください。

webpack.config.js

const path = require('path')
const assetsDir = path.resolve(__dirname, 'public_html/assets');

module.exports = {
	entry: assetsDir + "/src/app.tsx",
	output: {
		path: assetsDir + "/dist",
		filename: 'app.js'
	},
	module: {
		rules: [
			{
			test: /\.tsx?$/,
			use: "ts-loader",
			},
		],
	},
	resolve: {
	extensions: [".ts", ".tsx", ".js"]
	},
};

これでWebpackの準備は完了です。

ビルドしてみる

簡単なファイルを作ってjsにビルドできるか確認してみましょう。
webpack.config.jsで設定したディレクトリを作成しましょう。

$ mkdir -p public_html/assets/src

エントリーポイントで指定したファイルの作成。

$ touch public_html/assets/src/app.tsx

ファイルを次のように編集します。

public_html/assets/src/app.tsx

import React from 'react'
import { render } from 'react-dom'

const App = () => {
	return (
		<div>
			<div>Hello React</div>
		</div>
	)
}

render(<App/>, document.querySelector('#app'))

ビルドを実行します。

$ npm run build

startを実行することでファイルを監視して、編集するたびに自動的にビルドを実行することもできます。

$ npm run start

ここまで設定できているのであれば、public_html/assets/distディレクトリにapp.jsが生成されているはずです。

次にindex.htmlを作成し、ビルドしたapp.jsを実行してみます。

public_html/index.html

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>部分React</title>
</head>
<body>
	<div id="app"></div>
	<script src="assets/dist/app.js" charset="utf-8"></script>
</body>
</html>

ブラウザでindex.htmlを開いてください。
「Hello React」が表示されていれば成功です。

複数のコンポーネントを扱う

これで好きなディレクトリにソースを配置したり、書き出したりということはできるようになりましたが、ルートのAppコンポーネントを表示しているだけなのでSPAと変わらないですね。
複数のコンポーネントをhtmlで自由に表示したいことがあると思います。

componentsディレクトリを作り表示したいコンポーネントを作成しましょう。
ここではHeader.tsxFooter.tsxを作成します。

public_html/assets/src/components/Header.tsx

import React from 'react'

const Header = () => {
	return (
	<header>
		<h1>Header</h1>
	</header>
	)
}

export default Header

public_html/assets/src/components/Footer.tsx

import React from 'react'

const Footer = () => {
	return (
	<footer>
		<p>Footer</p>
	</footer>
	)
}

export default Footer

app.tsxを次のように編集します。

public_html/assets/src/app.tsx

import React from 'react'
import { render } from 'react-dom'
import Header from './components/Header'
import Footer from './components/Footer'

const headerDOM = document.getElementById('header')
if (headerDOM !== null) render(<Header />, headerDOM)


const footerDOM = document.getElementById('footer')
if (footerDOM !== null) render(<Footer />, footerDOM)

これでhtmlファイルでidにheaderfooterを指定することでコンポーネントを表示することができるようになります。

public_html/index.html

<body>
    <div id="header"></div>
    <div id="footer"></div>
    <script src="assets/dist/app.js" charset="utf-8"></script>
</body>

React本体をWebpackしない

実際のソースコードは10Kでも、ビルドしたファイルはReact本体(大きいのはReactDOM)を含むので100Kくらいになっていると思います。
SPAとしてならすべて一つのファイルにまとめるのが一般的ですが、この記事の用途だと作成したソースだけビルドしたいとうケースもあると思います。
その場合webpack.config.jsexternalsにReactとReactDOMを指定します。

webpack.config.js

const path = require('path')
const assetsDir = path.resolve(__dirname, 'public_html/assets');

module.exports = {
	entry: assetsDir + "/src/app.tsx",
	output: {
	path: assetsDir + "/dist",
	filename: 'app.js'
	},
	module: {
	rules: [
		{
		test: /\.tsx?$/,
		use: "ts-loader",
		},
	],
	},
	resolve: {
	extensions: [".ts", ".tsx", ".js"]
	},
	externals: {
		'react': 'React',
		'react-dom': 'ReactDOM',
	}
};

この場合、app.jsを読み込む前にReactとReactDOMは別途読み込む必要があります。

<script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script>
<script src="assets/dist/app.js" charset="utf-8"></script>

React外の関数にアクセスする

すでに運用しているサイトの場合はなんらかのJavaScriptの処理だったりjQueryのプラグインが入っていると思います。
Reactから外部のJavaScriptの関数を実行したいケースもあると思います。

既存のJavaScriptの修正

既存のJavaScriptもそのまま使えるわけではなく、修正の必要があります。
JavaScriptにはwindowというグローバルで使用できるオブジェクトがあるので、Reactで使用したい処理をここに登録しておきます。

window.hoge = function() {
	// ここに処理を書く
	console.log('外部のJS');
}

Reactの修正

次にReact(TypeScript)で実行できるようにインターフェイスを作成します。

Window.ts

interface IWindow extends Window {
	hoge: () => void
}
declare const window: IWindow
export default window

あとは使用したいコンポーネントで次のように記述すれば実行できます。

tsx

import window from '../../Window'

window.hoge()

windowに登録しておけばどこからでもアクセスできますよという例でした。

参考サイト
最新版TypeScript+webpack 4の環境構築まとめ(React, Vue.js, Three.jsのサンプル付き) – ICS MEDIA
既存のウェブサイトに React を追加する