Closed101

EJSをやめてReactでHTMLを書く

しょっぱなからタイポしてた😂

$ npm init -y

npmの雛形を作る

{
  "name": "react-stati-generator",
  "version": "1.0.0",
  "license": "MIT",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}

いらない記述を削除して"private": true,を追記する

webpackは4.x系を使うので4.4を入れるcliは最新でも問題無さそう

$ npm i -D webpack@4.44.2 webpack-cli

webpackのconfigファイルを作成する

$ touch webpack.config.js

忘れないうちにコマンドを登録しておく

{
  "name": "react-stati-generator",
  "version": "1.0.0",
  "license": "MIT",
  "private": true,
  "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1",
+    "webpack-watch": "webpack -w",
+    "webpack-build": "webpack"
  },
  "devDependencies": {
    "webpack": "^4.44.2",
    "webpack-cli": "^4.2.0"
  }
}

普段はGUIから作るけど今回はコマンドでファイルを作ってみる

$ mkdir src && mkdir src/pages && touch src/pages/index.jsx

一応普通のjsも作っておく

$ mkdir src/js && touch src/js/main.js

適当にmain.jsに書いとく

main.js
console.log('test');

とりあえずいつも使ってる設定ファイルからいらないところを消して、webpack.config.jsの中身を書く

webpack.config.js
const webpack = require('webpack');
const path = require('path');

module.exports = () => {
  const MODE = process.env.NODE_ENV;
  const IS_DEVELOPMENT = process.env.NODE_ENV === 'development';
  const IS_PRODUCTION = process.env.NODE_ENV === 'production';

  return {
    mode: MODE,
    devtool: IS_DEVELOPMENT ? 'inline-source-map' : false,
    entry: {
      "dist/assets/js/main": "./src/js/main",
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.jsx', '.js'],
    },
    output: {
      filename: '[name].js',
      path: path.join(__dirname),
    },
    module: {},
    plugins: [
      new webpack.ProgressPlugin(),
    ],
  }
};

赤いびよびよが出てくるので@typesを入れる

$ npx typesync

@types/nodeもいるな?🤔

$ npm i -D @types/node

process.env.NODE_ENVを使うのでnpm scriptsを増やす

npm run allを入れる

$ npm i -D npm-run-all

とりあえずstartとbuildを作る

pakage.json
{
  "name": "react-stati-generator",
  "version": "1.0.0",
  "license": "MIT",
  "private": true,
  "scripts": {
+    "start": "NODE_ENV=development run-p _start",
+    "build": "NODE_ENV=production run-p _build",
    "webpack-watch": "webpack -w",
    "webpack-build": "webpack",
+    "_start": "run-s webpack-watch",
+    "_build": "run-s webpack-build"
  },
  "devDependencies": {
    "@types/node": "^14.14.10",
    "@types/webpack": "^4.41.25",
    "npm-run-all": "^4.1.5",
    "webpack": "^4.44.2",
    "webpack-cli": "^4.2.0"
  }
}

忘れないうちにReadMeに書いとかないと

react-static-generator

Command

Install

$ npm ci

Watch

$ npm start

Build

$ npm run build

コマンドが動くか確認する

npm start
[webpack-cli] Compilation starting...
98% after emitting[webpack-cli] Compilation finished
Hash: af324363234bc97f2776
Version: webpack 4.44.2
Time: 60ms
Built at: 2020/12/06 16:30:02
                 Asset      Size               Chunks             Chunk Names
dist/assets/js/main.js  8.62 KiB  dist/assets/js/main  [emitted]  dist/assets/js/main
Entrypoint dist/assets/js/main = dist/assets/js/main.js
[./src/js/main.js] 20 bytes {dist/assets/js/main} [built]
[webpack-cli] watching files for updates...
npm run build
$ NODE_ENV=production run-p _build
$ run-s webpack-build
$ webpack
98% after emitting SizeLimitsPlugin[webpack-cli] Compilation finished
Hash: b1ec4be4a855a7482c28
Version: webpack 4.44.2
Time: 188ms
Built at: 2020/12/06 16:30:12
                 Asset       Size  Chunks             Chunk Names
dist/assets/js/main.js  949 bytes       0  [emitted]  dist/assets/js/main
Entrypoint dist/assets/js/main = dist/assets/js/main.js
[0] ./src/js/main.js 20 bytes {0} [built]
✨  Done in 2.70s.

コミットしておく

gitignore作るの忘れたから危うくnode_modules上げるところだった😂

$ touch .gitignore
.gitignore
node_modules

一応distもignoreしておこう🤔

.gitignore
node_modules
+ dist

まだ本編にすら到達していない😂

tsxでやりたいから全部リネームするか
まずはts-loaderの設定をちゃちゃっと

$ npm i -D ts-loader fork-ts-checker-webpack-plugin

めんどくさいので設定は普段使ってるやつを持ってくる

$ touch tsconfig.json
tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "jsx": "react",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "incremental": true,
    "tsBuildInfoFile": "./.cache/ts/.tsbuildinfo",
    "importHelpers": true,
    "downlevelIteration": true,
    "lib": ["dom", "es2015", "dom.iterable"],
    "typeRoots": ["src/@types", "node_modules/@types"],
    "baseUrl": ".",
    "strict": true,
    "moduleResolution": "node",
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "forceConsistentCasingInFileNames": true,
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true
  }
}

webpack.config.js
const webpack = require('webpack');
const path = require('path');
+const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = () => {
  const MODE = process.env.NODE_ENV;
  const IS_DEVELOPMENT = process.env.NODE_ENV === 'development';
  const IS_PRODUCTION = process.env.NODE_ENV === 'production';

  return {
    mode: MODE,
    devtool: IS_DEVELOPMENT ? 'inline-source-map' : false,
    entry: {
      "dist/assets/js/main": "./src/js/main",
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.jsx', '.js'],
    },
    output: {
      filename: '[name].js',
      path: path.join(__dirname),
    },
    module: {
+      rules: [
+        {
+          test: /\.(tsx?|jsx?)$/,
+          use: [
+            {
+              loader: 'ts-loader',
+              options: {
+                transpileOnly: true,
+                experimentalWatchApi: true,
+              }
+            }
+          ],
+        }
+      ]
    },
    plugins: [
+      new ForkTsCheckerWebpackPlugin(),
      new webpack.ProgressPlugin(),
    ],
  }
};

ここも普段はGUIだけどCUIで拡張子を変えてみる

$ mv src/js/main.js src/js/main.ts && mv src/pages/index.jsx src/pages/index.tsx

ちゃんとtypescriptかどうか適当にテストしてみる

main.ts
function test(args: number): number {
  return args;
}

console.log(test(3));
console.log(test('3'));
$ NODE_ENV=production run-p _build
$ run-s webpack-build
$ webpack
94% after seal[webpack-cli] Compilation finished
Hash: c4ee805885803dff91a5
Version: webpack 4.44.2
Time: 1038ms
Built at: 2020/12/06 16:56:39
                 Asset       Size  Chunks  Chunk Names
dist/assets/js/main.js  976 bytes       0  dist/assets/js/main
Entrypoint dist/assets/js/main = dist/assets/js/main.js
[0] ./src/js/main.ts 101 bytes {0} [built]

ERROR in src/js/main.ts:6:18
TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
    4 |
    5 | console.log(test(3));
  > 6 | console.log(test('3'));
      |                  ^^^

うまくいったのでmain.tsの内容をエラーが起きないようにする

main.ts
console.log('ああああああああ');

なんかこのスクラップハンズオンでなにかするのに良さそう(?)

tsに変えた分を全部コミットしておく

.cache/ts/.tsbuildinfoはgitで共有する必要が無いのでignoreする

.gitignore
node_modules
dist
+.cache

やっと本編!

html-webpack-pluginとReactを入れる

$ npm i -D html-webpack-plugin

Reactは普通に使うかもしれないので、dependenciesに入れる

$ npm i react react-dom

@typesはめんどくさいのでnpx typesyncで一気に入れる

$ npx typesync

webpacckとindex.tsxを書き換える

webpack.config.js
const webpack = require('webpack');
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
+const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = () => {
  const MODE = process.env.NODE_ENV;
  const IS_DEVELOPMENT = process.env.NODE_ENV === 'development';
  const IS_PRODUCTION = process.env.NODE_ENV === 'production';

  return {
    mode: MODE,
    devtool: IS_DEVELOPMENT ? 'inline-source-map' : false,
    entry: {
      "dist/assets/js/main": "./src/js/main",
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.jsx', '.js'],
    },
    output: {
      filename: '[name].js',
      path: path.join(__dirname),
    },
    module: {
      rules: [
        {
          test: /\.(tsx?|jsx?)$/,
          use: [
            {
              loader: 'ts-loader',
              options: {
                transpileOnly: true,
                experimentalWatchApi: true,
              }
            }
          ],
        }
      ]
    },
    plugins: [
+      new HtmlWebpackPlugin({
+        title: "My App",
+        template: "src/pages/index.tsx",
+      }),
      new ForkTsCheckerWebpackPlugin(),
      new webpack.ProgressPlugin(),
    ],
  }
};

index.tsx
import React from "react";
import { renderToStaticMarkup } from "react-dom/server";

const App = () => <div>Use React !!</div>;

export default ({ htmlWebpackPlugin }) => `
  <!DOCTYPE html>
  <html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${htmlWebpackPlugin.options.title}</title>
  </head>
  <body>
    ${renderToStaticMarkup(<App />)}
  </body>
  </html>
`;
$ NODE_ENV=production run-p _build
$ run-s webpack-build
$ webpack
94% after seal[webpack-cli] Compilation finished
Hash: 85dd6deab4f9bfb87e91
Version: webpack 4.44.2
Time: 1330ms
Built at: 2020/12/06 17:31:55
                 Asset       Size  Chunks  Chunk Names
dist/assets/js/main.js  984 bytes       0  dist/assets/js/main
Entrypoint dist/assets/js/main = dist/assets/js/main.js
[0] ./src/js/main.ts 55 bytes {0} [built]

ERROR in src/pages/index.tsx:1:19
TS7016: Could not find a declaration file for module 'react'. '/Users/name/github/react-static-generator/node_modules/react/index.js' implicitly has an 'any' type.
  Try `npm install @types/react` if it exists or add a new declaration (.d.ts) file containing `declare module 'react';`
  > 1 | import React from "react";
      |                   ^^^^^^^
    2 | import { renderToStaticMarkup } from "react-dom/server";
    3 |
    4 | const App = () => <div>Use React !!</div>;

ERROR in src/pages/index.tsx:2:38
TS7016: Could not find a declaration file for module 'react-dom/server'. '/Users/name/github/react-static-generator/node_modules/react-dom/server.js' implicitly has an 'any' type.
  Try `npm install @types/react-dom` if it exists or add a new declaration (.d.ts) file containing `declare module 'react-dom/server';`
    1 | import React from "react";
  > 2 | import { renderToStaticMarkup } from "react-dom/server";
      |                                      ^^^^^^^^^^^^^^^^^^
    3 |
    4 | const App = () => <div>Use React !!</div>;
    5 |

ERROR in src/pages/index.tsx:4:19
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
    2 | import { renderToStaticMarkup } from "react-dom/server";
    3 |
  > 4 | const App = () => <div>Use React !!</div>;
      |                   ^^^^^
    5 |
    6 | export default ({ htmlWebpackPlugin }) => `
    7 |   <!DOCTYPE html>

ERROR in src/pages/index.tsx:4:36
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
    2 | import { renderToStaticMarkup } from "react-dom/server";
    3 |
  > 4 | const App = () => <div>Use React !!</div>;
      |                                    ^^^^^^
    5 |
    6 | export default ({ htmlWebpackPlugin }) => `
    7 |   <!DOCTYPE html>

ERROR in src/pages/index.tsx:6:19
TS7031: Binding element 'htmlWebpackPlugin' implicitly has an 'any' type.
    4 | const App = () => <div>Use React !!</div>;
    5 |
  > 6 | export default ({ htmlWebpackPlugin }) => `
      |                   ^^^^^^^^^^^^^^^^^
    7 |   <!DOCTYPE html>
    8 |   <html lang="ja">
    9 |   <head>
Child HtmlWebpackCompiler:
                          Asset      Size  Chunks  Chunk Names
    __child-HtmlWebpackPlugin_0  33.5 KiB       0  HtmlWebpackPlugin_0
    Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0
    [0] ./node_modules/react/index.js 190 bytes {0} [built]
    [1] ./node_modules/object-assign/index.js 2.06 KiB {0} [built]
    [2] ./node_modules/react-dom/server.browser.js 228 bytes {0} [built]
    [3] ./node_modules/html-webpack-plugin/lib/loader.js!./src/pages/index.tsx 601 bytes {0} [built]
    [4] ./node_modules/react/cjs/react.production.min.js 6.3 KiB {0} [built]
    [5] ./node_modules/react-dom/cjs/react-dom-server.browser.production.min.js 19.8 KiB {0} [built]

めちゃくちゃ怒られた

Try `npm install @types/react` if it exists or add a new declaration (.d.ts) file containing `declare module 'react';

typesyncしたのに@types/reactが無いらしいのでもう一度npm installしてみる

とりあえずエラーを消していく
FC付ける必要あるのかわからないけどとりあえず

index.tsx
+import React, {FC} from "react";
import {renderToStaticMarkup} from "react-dom/server";

+const App: FC = () => <div>Use React !!</div>;

export default ({htmlWebpackPlugin}) => `
  <!DOCTYPE html>
  <html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${htmlWebpackPlugin.options.title}</title>
  </head>
  <body>
    ${renderToStaticMarkup(<App/>)}
  </body>
  </html>
`;

export default ({htmlWebpackPlugin}) => の型がわからないけど、html-webpack-pluginの型定義を見に行くと

html-webpack-plugin
  /**
   * The plugin options after adding default values
   */
  interface ProcessedOptions {
    ...
  }

この部分っぽい

index.tsx
import React, {FC} from "react";
import {renderToStaticMarkup} from "react-dom/server";
+import htmlWebpackPlugin from 'html-webpack-plugin';

const App: FC = () => <div>Use React !!</div>;

+export default ({htmlWebpackPlugin}: { htmlWebpackPlugin: htmlWebpackPlugin.ProcessedOptions }) => `
  <!DOCTYPE html>
  <html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${htmlWebpackPlugin.options.title}</title>
  </head>
  <body>
    ${renderToStaticMarkup(<App/>)}
  </body>
  </html>
`;

とりあえず、エラーは全部消えた

$ NODE_ENV=production run-p _build
$ run-s webpack-build
$ webpack
98% after emitting SizeLimitsPlugin[webpack-cli] Compilation finished
Hash: 87c7784d007b2445c334
Version: webpack 4.44.2
Time: 1765ms
Built at: 2020/12/06 17:41:55
                 Asset       Size  Chunks             Chunk Names
dist/assets/js/main.js  984 bytes       0  [emitted]  dist/assets/js/main
            index.html  243 bytes          [emitted]
Entrypoint dist/assets/js/main = dist/assets/js/main.js
[0] ./src/js/main.ts 55 bytes {0} [built]
Child HtmlWebpackCompiler:
                          Asset      Size  Chunks  Chunk Names
    __child-HtmlWebpackPlugin_0  46.1 KiB       0  HtmlWebpackPlugin_0
    Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0
    [0] ./node_modules/react/index.js 193 bytes {0} [built]
    [1] ./node_modules/object-assign/index.js 2.43 KiB {0} [built]
    [2] ./node_modules/react-dom/server.browser.js 231 bytes {0} [built]
    [3] ./node_modules/html-webpack-plugin/lib/loader.js!./src/pages/index.tsx 601 bytes {0} [built]
    [4] ./node_modules/react/cjs/react.production.min.js 8.12 KiB {0} [built]
    [5] ./node_modules/react-dom/cjs/react-dom-server.browser.production.min.js 30.2 KiB {0} [built]
✨  Done in 4.78s.

一旦コミットしておこう

index.htmlをdistに吐き出したいので設定を変える

タイトルいらないので消す
scriptの自動挿入もいらないので消す

webpack.config.js
const webpack = require('webpack');
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = () => {
  const MODE = process.env.NODE_ENV;
  const IS_DEVELOPMENT = process.env.NODE_ENV === 'development';
  const IS_PRODUCTION = process.env.NODE_ENV === 'production';

  return {
    mode: MODE,
    devtool: IS_DEVELOPMENT ? 'inline-source-map' : false,
    entry: {
      "dist/assets/js/main": "./src/js/main",
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.jsx', '.js'],
    },
    output: {
      filename: '[name].js',
      path: path.join(__dirname),
    },
    module: {
      rules: [
        {
          test: /\.(tsx?|jsx?)$/,
          use: [
            {
              loader: 'ts-loader',
              options: {
                transpileOnly: true,
                experimentalWatchApi: true,
              }
            }
          ],
        }
      ]
    },
    plugins: [
      new HtmlWebpackPlugin({
-        title: "My App",
        template: "src/pages/index.tsx",
+        filename: 'dist/index.html',
+        inject: false,
      }),
      new ForkTsCheckerWebpackPlugin(),
      new webpack.ProgressPlugin(),
    ],
  }
};

$ NODE_ENV=production run-p _build
$ run-s webpack-build
$ webpack
98% after emitting SizeLimitsPlugin[webpack-cli] Compilation finished
Hash: 87c7784d007b2445c334
Version: webpack 4.44.2
Time: 1357ms
Built at: 2020/12/06 17:52:24
                 Asset       Size  Chunks             Chunk Names
dist/assets/js/main.js  984 bytes       0  [emitted]  dist/assets/js/main
       dist/index.html  202 bytes          [emitted]
Entrypoint dist/assets/js/main = dist/assets/js/main.js
[0] ./src/js/main.ts 55 bytes {0} [built]
Child HtmlWebpackCompiler:
                          Asset      Size  Chunks  Chunk Names
    __child-HtmlWebpackPlugin_0  46.1 KiB       0  HtmlWebpackPlugin_0
    Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0
    [0] ./node_modules/react/index.js 193 bytes {0} [built]
    [1] ./node_modules/object-assign/index.js 2.43 KiB {0} [built]
    [2] ./node_modules/react-dom/server.browser.js 231 bytes {0} [built]
    [3] ./node_modules/html-webpack-plugin/lib/loader.js!./src/pages/index.tsx 601 bytes {0} [built]
    [4] ./node_modules/react/cjs/react.production.min.js 8.12 KiB {0} [built]
    [5] ./node_modules/react-dom/cjs/react-dom-server.browser.production.min.js 30.2 KiB {0} [built]
✨  Done in 3.40s.

distに出てきた!

pages/の中でheadを書きたくないのでLayoutsを作る

$ mkdir src/layouts && touch src/layouts/index.tsx

先にさっきの吐き出し先を変えた変更をpushしとこう

とりあえずレイアウトに切り出し

src/layouts/index.tsx
import React, {FC, ReactNode} from 'react'

type LayoutType = {
  children: ReactNode
}

export const Layout: FC<LayoutType> = ({children}) => {
  return (
    <>
      <html lang="ja">
      <head>
        <meta charSet="UTF-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
        <title>タイトル</title>
      </head>
      <body>
      {children}
      </body>
      </html>
    </>
  )
}

残ったもの

src/pages/index.tsx
import React from 'react';
import {renderToStaticMarkup} from 'react-dom/server';
import {Layout} from "../layouts";

export default () => {
  return '<!DOCTYPE html>' + renderToStaticMarkup(
    <Layout>
      テスト
    </Layout>
  )
};

Reactで<!DOCTYPE html>できない?よくわからないから

return '<!DOCTYPE html>' + renderToStaticMarkup(
    <Layout>
      テスト
    </Layout>
  )

こうしたけど、ダサいな🤔

renderToStaticMarkup()をwrapした関数を作る事で(いいかどうかは置いといて)とりあえず、解決だ😎

名前も思いつかないしhooksフォルダに入れても良いかわからないけどとりあえず作る

$ mkdir src/hooks && touch src/hooks/index.ts && touch src/hooks/staticRender.ts

renderToStaticMarkupなのでmyRenderToStaticMarkupにする

$ mv src/hooks/staticRender.ts src/hooks/myRenderToStaticMarkup.ts

renderToStaticMarkupのwrapper

myRenderToStaticMarkup
import {renderToStaticMarkup} from 'react-dom/server';
import {ReactElement} from "react";

export function myRenderToStaticMarkup(element: ReactElement): string {
  return '<!DOCTYPE html>' + renderToStaticMarkup(element);
}

hooksのエントリーポイント

src/hooks/index.ts
export {myRenderToStaticMarkup} from './myRenderToStaticMarkup';

pagesのindex.tsx

src/pages/index.tsx
import React from 'react';
-import {renderToStaticMarkup} from 'react-dom/server';
+import {myRenderToStaticMarkup} from "../hooks";
import {Layout} from "../layouts";

export default () => {
+  return myRenderToStaticMarkup(
    <Layout>
      テスト
    </Layout>
+  )
};

myRenderToStaticMarkupの型はここから取ってきた

node_modules/@types/react-dom/server/index.d.ts
/**
 * Similar to `renderToString`, except this doesn't create extra DOM attributes
 * such as `data-reactid`, that React uses internally. This is useful if you want
 * to use React as a simple static page generator, as stripping away the extra
 * attributes can save lots of bytes.
 */
export function renderToStaticMarkup(element: ReactElement): string;

他のページ今回はabout pageも出てくるようにする

その前にコミット

名前が思いつかないので一旦作る

$ mkdir .config && mkdir .config/webpack && touch .config/webpack/glob.js

目標

webpack.config.js
    plugins: [
-      new HtmlWebpackPlugin({
-        template: "src/pages/index.tsx",
-        filename: 'dist/index.html',
-        inject: false,
-      }),
+      pages.forEach((page) => {
+        new HtmlWebpackPlugin(page)
+      }),
      new ForkTsCheckerWebpackPlugin(),
      new webpack.ProgressPlugin(),
    ],

とりあえず名前はpagesでいいので変更

$ mv .config/webpack/glob.js .config/webpack/pages.js

中身
globを使ってsrc/pages/**/.tsxをすべて取得しmapで加工して返す

.config/webpack/pages.js
const glob = require('glob');

const pages = glob.sync(`**/*.tsx`, {
  ignore: `**/_*.tsx`,
  cwd: `./src/pages`
}).map((n) => {
  const ext = n.replace(/tsx$/,'html')
  return {
    template: "src/pages/" + n,
    filename: 'dist/' + ext,
    inject: false,
  }
});

module.exports = pages;

変数名とか雑だけど、とりあえずこれでいいっしょ

webpack.config.js
const webpack = require('webpack');
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const HtmlWebpackPlugin = require("html-webpack-plugin");
+const Pages = require(path.resolve('.config/webpack/pages.js'));

module.exports = () => {
  console.log(Pages);
  const MODE = process.env.NODE_ENV;
  const IS_DEVELOPMENT = process.env.NODE_ENV === 'development';
  const IS_PRODUCTION = process.env.NODE_ENV === 'production';

  return {
    mode: MODE,
    devtool: IS_DEVELOPMENT ? 'inline-source-map' : false,
    entry: {
      "dist/assets/js/main": "./src/js/main",
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.jsx', '.js'],
    },
    output: {
      filename: '[name].js',
      path: path.join(__dirname),
    },
    module: {
      rules: [
        {
          test: /\.(tsx?|jsx?)$/,
          use: [
            {
              loader: 'ts-loader',
              options: {
                transpileOnly: true,
                experimentalWatchApi: true,
              }
            }
          ],
        }
      ]
    },
    plugins: [
-        new HtmlWebpackPlugin({
-        template: "src/pages/index.tsx",
-        filename: 'dist/index.html',
-        inject: false,
-      }),
+      Pages.forEach((page) => {
+        new HtmlWebpackPlugin(page);
+      }),
      new ForkTsCheckerWebpackPlugin(),
      new webpack.ProgressPlugin(),
    ],
  }
};

😇😇😇😇😇😇

$ NODE_ENV=production run-p _build
$ run-s webpack-build
$ webpack
[
  {
    template: 'src/pages/about/index.tsx',
    filename: 'dist/about/index.html',
    inject: false
  },
  {
    template: 'src/pages/index.tsx',
    filename: 'dist/index.html',
    inject: false
  }
]
[webpack-cli] Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
 - configuration.plugins[1] should be one of these:
   object { apply, … } | function
   -> Plugin of type object or instanceof Function
   Details:
    * configuration.plugins[1] should be an object.
      -> Plugin instance
    * configuration.plugins[1] should be an instance of function
      -> Function acting as plugin

forEachじゃだめみたいなので、mapでnew HtmlWebpackPluginを返してpluginsにマージさせればいいので

webpack.config.js
const webpack = require('webpack');
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const Pages = require(path.resolve('.config/webpack/pages.js'));

module.exports = () => {
-  const test = Pages.map((page) => new HtmlWebpackPlugin(page));←debugした時の残骸
-  console.log(test);←debugした時の残骸
  const MODE = process.env.NODE_ENV;
  const IS_DEVELOPMENT = process.env.NODE_ENV === 'development';
  const IS_PRODUCTION = process.env.NODE_ENV === 'production';

  return {
    mode: MODE,
    devtool: IS_DEVELOPMENT ? 'inline-source-map' : false,
    entry: {
      "dist/assets/js/main": "./src/js/main",
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.jsx', '.js'],
    },
    output: {
      filename: '[name].js',
      path: path.join(__dirname),
    },
    module: {
      rules: [
        {
          test: /\.(tsx?|jsx?)$/,
          use: [
            {
              loader: 'ts-loader',
              options: {
                transpileOnly: true,
                experimentalWatchApi: true,
              }
            }
          ],
        }
      ]
    },
    plugins: [
-      Pages.forEach((page) => {
-        new HtmlWebpackPlugin(page);
-      }),
+      ...Pages.map((page) => new HtmlWebpackPlugin(page)),
+      ...[
        new ForkTsCheckerWebpackPlugin(),
        new webpack.ProgressPlugin(),
+      ]
    ],
  }
};

無事about pageも出るようになりました😤

$ NODE_ENV=production run-p _build
$ run-s webpack-build
$ webpack
98% after emitting SizeLimitsPlugin[webpack-cli] Compilation finished
Hash: f18a6300809936ad70c8
Version: webpack 4.44.2
Time: 1403ms
Built at: 2020/12/06 19:20:02
                 Asset       Size  Chunks             Chunk Names
 dist/about/index.html  179 bytes          [emitted]
dist/assets/js/main.js  984 bytes       0  [emitted]  dist/assets/js/main
       dist/index.html  177 bytes          [emitted]
Entrypoint dist/assets/js/main = dist/assets/js/main.js
[0] ./src/js/main.ts 55 bytes {0} [built]
Child HtmlWebpackCompiler:
                          Asset      Size  Chunks  Chunk Names
    __child-HtmlWebpackPlugin_0  47.5 KiB       0  HtmlWebpackPlugin_0
    __child-HtmlWebpackPlugin_1  47.5 KiB       1  HtmlWebpackPlugin_1
    Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0
    Entrypoint HtmlWebpackPlugin_1 = __child-HtmlWebpackPlugin_1
    [0] ./node_modules/react/index.js 193 bytes {0} {1} [built]
    [1] ./node_modules/object-assign/index.js 2.43 KiB {0} {1} [built]
    [2] ./node_modules/react-dom/server.browser.js 231 bytes {0} {1} [built]
    [3] ./src/layouts/index.tsx 575 bytes {0} {1} [built]
    [4] ./src/hooks/index.ts + 1 modules 243 bytes {0} {1} [built]
        | ./src/hooks/index.ts 67 bytes [built]
        | ./src/hooks/myRenderToStaticMarkup.ts 171 bytes [built]
    [5] ./node_modules/react/cjs/react.production.min.js 8.12 KiB {0} {1} [built]
    [6] ./node_modules/react-dom/cjs/react-dom-server.browser.production.min.js 30.2 KiB {0} {1} [built]
    [7] ./node_modules/html-webpack-plugin/lib/loader.js!./src/pages/about/index.tsx 234 bytes {0} [built]
    [8] ./node_modules/html-webpack-plugin/lib/loader.js!./src/pages/index.tsx 241 bytes {1} [built]
✨  Done in 3.16s.

というかglobってビルドインだったっけ?間違えてグローバルに入れちゃっててそれ参照してるのかも?

ということでglobを入れる

$ npm i glob -D

ちょっと分割代入に変えたほうがオプション渡しやすいから変えよう

.config/webpack/pages.js
const glob = require('glob');

const pages = glob.sync(`**/*.tsx`, {
  ignore: `**/_*.tsx`,
  cwd: `./src/pages`
}).map((n) => {
  const ext = n.replace(/tsx$/,'html')
  return {
    template: "src/pages/" + n,
    filename: 'dist/' + ext,
-    inject: false,
  }
});

module.exports = pages;

webpack.config.js
const webpack = require('webpack');
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const Pages = require(path.resolve('.config/webpack/pages.js'));

module.exports = () => {
  const MODE = process.env.NODE_ENV;
  const IS_DEVELOPMENT = process.env.NODE_ENV === 'development';
  const IS_PRODUCTION = process.env.NODE_ENV === 'production';

  return {
    mode: MODE,
    devtool: IS_DEVELOPMENT ? 'inline-source-map' : false,
    entry: {
      "dist/assets/js/main": "./src/js/main",
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.jsx', '.js'],
    },
    output: {
      filename: '[name].js',
      path: path.join(__dirname),
    },
    module: {
      rules: [
        {
          test: /\.(tsx?|jsx?)$/,
          use: [
            {
              loader: 'ts-loader',
              options: {
                transpileOnly: true,
                experimentalWatchApi: true,
              }
            }
          ],
        }
      ]
    },
    plugins: [
+      ...Pages.map(({template, filename}) => new HtmlWebpackPlugin({
+        template,
+        filename,
+        inject: false,
+      })),
      ...[
        new ForkTsCheckerWebpackPlugin(),
        new webpack.ProgressPlugin(),
      ]
    ],
  }
};

そういえばせっかく作ったmain.jsを読み込んでなかった

いつものBrowserSync入れよう

$ npm i -D browser-sync
$ touch .config/bs-config.js
.config/bs-config.js
const BROWSER_SYNC = {
  files: [
    `dist/assets/**/*`,
    `dist/**/*.html`,
  ],
  ghostMode: {
    clicks: false,
    scroll: false,
    forms: false
  },
  server: {
    baseDir: 'dist',
    middleware: [],
  },
  logFileChanges: false
};

module.exports = BROWSER_SYNC;
pakage.json
{
  "name": "react-stati-generator",
  "version": "1.0.0",
  "license": "MIT",
  "private": true,
  "scripts": {
+    "start": "NODE_ENV=development run-p _start server",
    "build": "NODE_ENV=production run-p _build",
+    "server": "browser-sync start --config ./.config/bs-config.js",
    "webpack-watch": "webpack -w",
    "webpack-build": "webpack",
    "_start": "run-s webpack-watch",
    "_build": "run-s webpack-build"
  }
}

疲れたの今日はここまで

typescriptが無いっぽいので入れる

$ npm i typescript -D

pathの情報をpage全体で使用したのでuseContextを使う

$ touch src/config.ts
src/config.ts
import React from "react";

type currentPageValueType = {
  path: '/' | './' | '../' | '../../',
}

const currentPageValue: currentPageValueType = {
  path: '/',
}

export const CurrentPage = React.createContext(currentPageValue);

とりあえずpagesに反映させるためにProviderで囲う

src/pages/index.tsx
 import React from 'react';
 import {myRenderToStaticMarkup} from "../hooks";
 import {Layout} from "../layouts";
+import {CurrentPage} from "../config";
 
 export default () => {
   return myRenderToStaticMarkup(
-    <Layout>
-      テスト
-    </Layout>
+    <CurrentPage.Provider value={{path: './'}}>
+      <Layout>
+        テスト
+      </Layout>
+    </CurrentPage.Provider>
   )
 };

src/pages/about/index.tsx
 import React from 'react';
 import {myRenderToStaticMarkup} from "../../hooks";
 import {Layout} from "../../layouts";
+import {CurrentPage} from "../../config";
 
 export default () => {
   return myRenderToStaticMarkup(
-    <Layout>
-      About
-    </Layout>
+    <CurrentPage.Provider value={{path: '../'} as const}>
+      <Layout>
+        about
+      </Layout>
+    </CurrentPage.Provider>
   )
 };

ちゃんとできているか確認するためにダミーのコンポーネントを作成する

$ mkdir src/components && touch src/components/index.ts && touch src/components/App.tsx
src/components/App.tsx
import React, {useContext} from "react";
import {CurrentPage} from "../config";

export const App = () => {
  const {path} = useContext(CurrentPage);
  return (
    <div>
      {path}
    </div>
  )
}
src/components/index.ts
export {App} from './App';
src/pages/index.tsx
import {myRenderToStaticMarkup} from "../hooks";
import {Layout} from "../layouts";
import {CurrentPage} from "../config";
+import {App} from "../components";

export default () => {
  return myRenderToStaticMarkup(
    <CurrentPage.Provider value={{path: './'}}>
      <Layout>
        テスト
+        <App/>
      </Layout>
    </CurrentPage.Provider>
  )

src/pages/about/index.tsx
import {myRenderToStaticMarkup} from "../../hooks";
import {Layout} from "../../layouts";
import {CurrentPage} from "../../config";
+import {App} from "../../components";

export default () => {
  return myRenderToStaticMarkup(
    <CurrentPage.Provider value={{path: '../'} as const}>
      <Layout>
        about
+        <App/>
      </Layout>
    </CurrentPage.Provider>
  )

pagesを起点に現在のページの相対pathを割り出す

フォルダから相対パスを出すのは履修済みなので実装する

pathをrequireしてpath.relative()してやればいい

.config/webpacl/page
 const glob = require('glob');
+const path = require('path');
+
+const PAGE_ROOT = 'src/pages/';
 
 const pages = glob.sync(`**/*.tsx`, {
   ignore: `**/_*.tsx`,
-  cwd: `./src/pages`
-}).map((n) => {
-  const ext = n.replace(/tsx$/,'html')
+  cwd: PAGE_ROOT
+}).map((currentPagePath) => {
+  const pageCurrentPath = PAGE_ROOT + currentPagePath.replace(/index\.tsx$/, '');
+  const relativePath = `${path.relative(pageCurrentPath, PAGE_ROOT) || '.'}/`;
+  const currentPageHTMLPath = currentPagePath.replace(/tsx$/, 'html')
   return {
-    template: "src/pages/" + n,
-    filename: 'dist/' + ext,
+    template: PAGE_ROOT,
+    filename: 'dist/' + currentPageHTMLPath,
+    relativePath
   }
 });
 

currentPagePath.replace(/index\.tsx$/, '')の部分が/index\.tsx$/なので、index以外もできるように今後変更したい
すぐに正規表現が思いつかない

というかこれどっかで一回やったことあるので探してみる

Gatsbyですべての画像から特定の画像を抜き出す時に使った((?!.*\/).+\.(png|jpe?g))$これを((?!.*\/).+\.tsx)$こうじゃ

余計な括弧もいらないからこうじゃ!(?!.*\/).+\.tsx$

.config/webpack/pages.js
   ignore: `**/_*.tsx`,
   cwd: PAGE_ROOT
 }).map((currentPagePath) => {
-  const pageCurrentPath = PAGE_ROOT + currentPagePath.replace(/index\.tsx$/, '');
+  const pageCurrentPath = PAGE_ROOT + currentPagePath.replace(/(?!.*\/).+\.tsx$/, '');
   const relativePath = `${path.relative(pageCurrentPath, PAGE_ROOT) || '.'}/`;
   const currentPageHTMLPath = currentPagePath.replace(/tsx$/, 'html')
   return {

とりあえず、適当に変数名も名前も読みやすく変えるのと
pathTypeを別の所でも使うので、抜き出せるようにする

src/confit.ts
 import React from "react";
 
-type currentPageValueType = {
-  path: '/' | './' | '../' | '../../',
+export type pathType = '/' | './' | '../' | '../../';
+
+type initialPageValueType = {
+  path: pathType,
 }
 
-const currentPageValue: currentPageValueType = {
+const initialPageValue: initialPageValueType = {
   path: '/',
 }
 
-export const CurrentPage = React.createContext(currentPageValue);
+export const CurrentPage = React.createContext(initialPageValue);

webpackで先程のrelativePathを受け取れるり、HtmlWebpackPluginに投げる

webpack.config.js
     plugins: [
-      ...Pages.map(({template, filename}) => new HtmlWebpackPlugin({
+      ...Pages.map(({template, filename, relativePath}) => new HtmlWebpackPlugin({
         template,
         filename,
+        relativePath,
         inject: false,
       })),
       ...[

投げ方的にtemplateParametersもあるみたいだけど、普通に投げても受け取れるみたいなので普通に投げる

一旦pages/index.tsxに読み込ませる

型とか分割代入で汚いのでなんとかしたい

src/pages/index.tsx
 import React from 'react';
+import htmlWebpackPlugin from 'html-webpack-plugin';
+import {pathType} from "../config";
 import {myRenderToStaticMarkup} from "../hooks";
 import {Layout} from "../layouts";
 import {CurrentPage} from "../config";
 import {App} from "../components";
 
-export default () => {
+export default ({htmlWebpackPlugin}: { htmlWebpackPlugin: htmlWebpackPlugin.ProcessedOptions }) => {
+  const relativePath: pathType = htmlWebpackPlugin.options.relativePath;
   return myRenderToStaticMarkup(
-    <CurrentPage.Provider value={{path: './'}}>
+    <CurrentPage.Provider value={{path: relativePath}}>
       <Layout>
         テスト
         <App/>

aboutにも読み込ませる

src/about/index.tsx
 import React from 'react';
+import htmlWebpackPlugin from 'html-webpack-plugin';
+import {pathType} from "../../config";
 import {myRenderToStaticMarkup} from "../../hooks";
 import {Layout} from "../../layouts";
 import {CurrentPage} from "../../config";
 import {App} from "../../components";
 
-export default () => {
+export default ({htmlWebpackPlugin}: { htmlWebpackPlugin: htmlWebpackPlugin.ProcessedOptions }) => {
+  const relativePath: pathType = htmlWebpackPlugin.options.relativePath;
   return myRenderToStaticMarkup(
-    <CurrentPage.Provider value={{path: '../'} as const}>
+    <CurrentPage.Provider value={{path: relativePath}}>
       <Layout>
         about
         <App/>

Providerまでは絶対共通なのでProviderまでを切り出す。

src/hooks/myRenderToStaticMarkup.ts
  import {renderToStaticMarkup} from 'react-dom/server';
  import React, {ReactElement} from "react";
  import {ProcessedOptions} from 'html-webpack-plugin';
  import {pathType} from "../config";
  import {CurrentPage} from "../config";
  
  function myRenderToStaticMarkup(element: ReactElement): string {
    return '<!DOCTYPE html>' + renderToStaticMarkup(element);
  }
  
  export function newRenderToStaticMarkup(element: ReactElement) {
    return (htmlWebpackPlugin: ProcessedOptions) => {
      const relativePath: pathType = htmlWebpackPlugin.options.relativePath;
      return myRenderToStaticMarkup(
        <CurrentPage.Provider value={{path: relativePath}}>
          {element}
        </CurrentPage.Provider>
      )
    }
  }

拡張子と名前が変わったので変える

$ mv src/hooks/myRenderToStaticMarkup.ts src/hooks/newRenderToStaticMarkup.tsx

なんとなくかっこよさでカリー化してみたけど、他にいい方法があるかもしれない。
ファイル分割とかその辺も

共通部分を切り出したのでだいぶスッキリした😎

src/pages/index.tsx
 import React from 'react';
-import htmlWebpackPlugin from 'html-webpack-plugin';
-import {pathType} from "../config";
-import {myRenderToStaticMarkup} from "../hooks";
+import {ProcessedOptions} from 'html-webpack-plugin';
+import {newRenderToStaticMarkup} from "../hooks";
 import {Layout} from "../layouts";
-import {CurrentPage} from "../config";
 import {App} from "../components";
 
-export default ({htmlWebpackPlugin}: { htmlWebpackPlugin: htmlWebpackPlugin.ProcessedOptions }) => {
-  const relativePath: pathType = htmlWebpackPlugin.options.relativePath;
-  return myRenderToStaticMarkup(
-    <CurrentPage.Provider value={{path: relativePath}}>
-      <Layout>
-        テスト
-        <App/>
-      </Layout>
-    </CurrentPage.Provider>
-  )
+export default ({htmlWebpackPlugin}: ProcessedOptions) => {
+  return newRenderToStaticMarkup(
+    <Layout>
+      テスト
+      <App/>
+    </Layout>
+  )(htmlWebpackPlugin);
 };

src/pages/about/index.tsx
 import React from 'react';
-import htmlWebpackPlugin from 'html-webpack-plugin';
-import {pathType} from "../../config";
-import {myRenderToStaticMarkup} from "../../hooks";
+import {ProcessedOptions} from 'html-webpack-plugin';
+import {newRenderToStaticMarkup} from "../../hooks";
 import {Layout} from "../../layouts";
-import {CurrentPage} from "../../config";
 import {App} from "../../components";
 
-export default ({htmlWebpackPlugin}: { htmlWebpackPlugin: htmlWebpackPlugin.ProcessedOptions }) => {
-  const relativePath: pathType = htmlWebpackPlugin.options.relativePath;
-  return myRenderToStaticMarkup(
-    <CurrentPage.Provider value={{path: relativePath}}>
-      <Layout>
-        about
-        <App/>
-      </Layout>
-    </CurrentPage.Provider>
-  )
+export default ({htmlWebpackPlugin}: ProcessedOptions) => {
+  return newRenderToStaticMarkup(
+    <Layout>
+      about
+      <App/>
+    </Layout>
+  )(htmlWebpackPlugin);
 };

もしかしてこれ引数の順番を変えると一発でいけるのでは?

この形にしないといけなさそうなので、だめかも?
export default ({htmlWebpackPlugin}) => test(htmlWebpackPlugin)(jsx);

いい加減pathを書くのがめんどくさいのでaliasを設定する

webpack.config.js
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.jsx', '.js'],
+      alias: {
+        'src': path.resolve('./src'),
+      },
    },
    output: {
      filename: '[name].js',
tsconfig.json
    "lib": ["dom", "es2015", "dom.iterable"],
    "typeRoots": ["src/@types", "node_modules/@types"],
    "baseUrl": ".",
+    "paths": {
+      "src/*": ["src/*"]
+    },
    "strict": true,
    "moduleResolution": "node",
    "noUnusedLocals": true,

リアタイじゃないけど追記

headタグを書くページで追記したい時の事を考えてreact-helmetを採用する

$ npm i -D react-helmet
$ npx typesync
$ npm i
renderToStaticMarkup(element);

の後に

const helmet = Helmet.renderStatic();

を追記してください!

const html = `
    <!doctype html>
    <html ${helmet.htmlAttributes.toString()}>
        <head>
            ${helmet.title.toString()}
            ${helmet.meta.toString()}
            ${helmet.link.toString()}
        </head>
        <body ${helmet.bodyAttributes.toString()}>
            <div id="content">
                // React stuff here
            </div>
        </body>
    </html>
`;

つまりこうじゃ!

src/layouts/index.tsx
+import {Helmet} from "react-helmet";

 export const Layout: FC<LayoutType> = ({children}) => {
   return (
     <>
-      <html lang="ja">
-      <head>
+      <Helmet>
+        <html lang="ja" />
         <meta charSet="UTF-8"/>
-        <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
         <title>タイトル</title>
-      </head>
+        <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+        <meta name="description" content="説明文"/>
+      </Helmet>
       <body>
       {children}
       </body>
-      </html>
     </>
   )
 }


src/hooks/newRenderToStaticMarkup.tsx
import {renderToStaticMarkup} from 'react-dom/server';
import React, {ReactElement} from "react";
import {Helmet} from "react-helmet";
import {ProcessedOptions} from 'html-webpack-plugin';
import {CurrentPage, pathType} from "src/config";

function myRenderToStaticMarkup(element: ReactElement): string {
  const staticMarkup = renderToStaticMarkup(element);
  const helmet = Helmet.renderStatic();

  function replaceDataHelmet(helmet: string) {
    return helmet.replace(/data-react-helmet="true"/g, '')
  }

  return `
    <!doctype html>
     <html ${helmet.htmlAttributes.toString()}>
      <head>
       ${replaceDataHelmet(helmet.title.toString())}
       ${replaceDataHelmet(helmet.meta.toString())}
       ${replaceDataHelmet(helmet.link.toString())}
      </head>
      ${staticMarkup}
    </html>
  `;
}

export function newRenderToStaticMarkup(element: ReactElement) {
  return (htmlWebpackPlugin: ProcessedOptions) => {
    const relativePath: pathType = htmlWebpackPlugin.options.relativePath;
    return myRenderToStaticMarkup(
      <CurrentPage.Provider value={{path: relativePath}}>
        {element}
      </CurrentPage.Provider>
    )
  }
}

renderToStaticMarkup(element);を返してた部分を変数に打ち込んで、doc通りにするbodyにclassは付けないのでbodyはLayoutの中のまま

data-react-helmet="true"はいらないので削除する

function replaceDataHelmet(helmet: string) {
  return helmet.replace(/data-react-helmet="true"/g, '')
}

年も変わったので、webpack5にする

$ npm install -g npm-check-updates
$ ncu
Upgrading /Users/hisho/github/react-static-generator/package.json
[====================] 19/19 100%

 @types/node                     ^14.14.10  →  ^14.14.17
 fork-ts-checker-webpack-plugin     ^6.0.5  →     ^6.0.8
 ts-loader                         ^8.0.11  →    ^8.0.13
 typescript                         ^4.1.2  →     ^4.1.3
 webpack                           ^4.44.2  →    ^5.11.1
 webpack-cli                        ^4.2.0  →     ^4.3.1

Run npm install to install new versions.
$ ncu -u
$ npm i
webpack.config.js
      filename: '[name].js',
      path: path.join(__dirname),
    },
+    target: ['web', 'es5'],
    module: {
      rules: [
        {

TODO

  • 各ページを一言管理するjsonの読み込み
  • ESlintの設定
  • htmlhintの設定
  • prettierの設定

pages.jsがバグっていたので、修正する

.config/webpack/pages.js
const glob = require('glob');
const path = require('path');

const PAGE_ROOT = 'src/pages/';

const pages = glob.sync(`**/*.tsx`, {
  ignore: `**/_*.tsx`,
  cwd: PAGE_ROOT
}).map((currentPagePath) => {
  const pageCurrentPath = PAGE_ROOT + currentPagePath.replace(/(?!.*\/).+\.tsx$/, '');
  const relativePath = `${path.relative(pageCurrentPath, PAGE_ROOT) || '.'}/`;
  const currentPageHTMLPath = currentPagePath.replace(/tsx$/, 'html')
  return {
-    template: PAGE_ROOT,
+    template: PAGE_ROOT + currentPagePath,
    filename: 'dist/' + currentPageHTMLPath,
    relativePath
  }
});

module.exports = pages;

このスクラップは2021/04/25にクローズされました
ログインするとコメントできます