ReactでHTMLのコーディングを行えるようにする
Reactで綺麗なHTMLが出力されるようにする。
モチベーション
以前Next.jsで納品したが、新しいプロジェクトでクライアント自身が触りたいのでHTMLで納品してほしいとのこと。
今までEJSを使っていたが、Reactの快適さに慣れてしまったのでReactで書けないか試してみる
プロジェクトの作成
プロジェクトディレクトリを作成後
npm init -y
yarn add minista react react-dom
記載してあるようにディレクトリを作成
.
├── yarn.lock
├── package.json
├── public
└── src
├── assets
│ ├── css
│ │ └── index.css
│ ├── index.js
│ └── js
│ └── hello.js
├── components
│ └── app-layout.js
└── pages
└── index.js
index.jsの中身を作成
import React from "react"; // Required!
import { render } from "minista"; // Required!
const Home = () => {
return render(
// Required!
<h1>Hello</h1>
);
};
export default Home;
package.jsonにministaのスクリプトを書く
"scripts": {
"dev": "minista",
"build": "minista build"
},
動作テストしてみると動いた!
CSSの動作チェック
CSSを記述
h1{
color:red;
}
jsで読み込み
import "./css/index.css";
CSSもOK
##Sassの準備
yarn add sass-loader sass
ファイル名のcssをscssに変更
devもbuildも問題なく動作した
Typescriptの準備
参考
Typescriptをインストールyarn add typescript ts-loader @types/react @types/react-dom
記事と同様にファイル設定をしていく。
VSCodeのエラーはこちらで解決
tsconfing.jsonのcompilerOptionsにoutdirかoutFileを指定するとエラーがなくなるようです。
変換しない場合は、"outFile": "" とするかまたは存在しないデタラメなフォルダを指定すればいいようです。
ライブラリ追加
yarn add babel-plugin-module-resolver
jsをtsx,tsに変更
tsxの動作テスト
ファイル作成
import React from "react";
import { render } from "minista";
import Title from "@/components/Title";
const Home = () => {
return render(<Title title="タイトルです" />);
};
export default Home;
import { VFC } from "react";
type Prop = {
title: string;
}
const Title: VFC<Prop> = (props) => {
return (<h1>{props.title}</h1>)
}
export default Title
動いた!
Webp対応
ライブラリのインストール
yarn add copy-webpack-plugin file-loader imagemin-webp-webpack-plugin
const path = require("path");
const glob = require("glob");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ImageminWebpWebpackPlugin = require("imagemin-webp-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const webpackConfig = {
entry: "./src/assets/index.ts",
module: {
rules: [
{
test: [/\.ts$/, /\.tsx$/, /\.js$/],
exclude: /node_modules/,
use: ["babel-loader", "ts-loader"],
},
+ {
+ test: /\.(jpe?g|png)$/i,
+ loader: "file-loader",
+ options: {
+ name: "[name].[ext]?[hash]",
+ outputPath: path.resolve(__dirname, "dist/asstes/img"),
+ },
},
],
},
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
},
- plugins:[]
+ plugins: [
+ new CopyWebpackPlugin({
+ patterns: [
+ {
+ from: "src/assets/img/",
+ to: path.resolve(__dirname, "dist/assets/img"),
+ },
+ ],
+ }),
+ new ImageminWebpWebpackPlugin({
+ config: [
+ {
+ test: /\.(jpe?g|png)$/i,
+ options: {
+ quality: 60,
+ },
+ },
+ ],
+ overrideExtension: false,
+ detailedLogs: true,
+ }),
+ ],
};
glob
.sync("**/*.tsx", {
cwd: "src/pages",
})
.forEach((file) => {
const extname = path.extname(file);
const basename = path.basename(file, extname);
const dirname = path.dirname(file);
webpackConfig.plugins.push(
new HtmlWebpackPlugin({
template: path.resolve("src/pages", file),
filename: path.join(dirname, basename + ".html"),
})
);
});
module.exports = webpackConfig;
Pictureコンポーネントを作成
webp対応はhtaccessではなくコンポーネントの方で対応します。
毎回書くのはだるいので、Pictureコンポーネントを作成。
import { VFC } from "react";
type Props = {
srcPc: string;
srcSp: string;
alt: string | undefined;
webp: boolean;
height: number;
width: number;
loadingLazy: boolean;
};
const Picture: VFC<Props> = (props) => {
return (
<picture>
{props.srcSp ? (
<source
type="image/webp"
media="(max-width: 767px)"
srcSet={`${props.srcSp}.webp`}
/>
) : null}
{props.webp && props.srcSp ? (
<source
type="image/webp"
media="(max-width: 767px)"
srcSet={`${props.srcSp}.webp`}
/>
) : null}
{props.webp ? (
<source type="image/webp" srcSet={`${props.srcPc}.webp`} />
) : null}
<img
src={props.srcPc}
alt={props.alt}
width={props.width}
height={props.height}
decoding="async"
loading={props.loadingLazy ? "lazy" : "eager"}
/>
</picture>
);
};
export default Picture;
こんな感じで使います
const Home = () => {
return render(
<>
<Title title="タイトルです" />
<Picture
webp={true} // webp対応するか
srcPc="./assets/img/noimage.png" // PCのパス
srcSp="./assets/img/noimage.png" // SPのパス
alt="" //alt属性
width={50} // width
height={100} // height
loadingLazy={true} // trueにしたらloading="lazy"を指定、通常decoding="async"が指定されている
/>
</>
);
};
残りタスク
autoprefixer
picturefill
object-fit
autoprefixerは入っているっぽい
package.jsonに対応ブラウザを記載
ポリフィルの追加
picturefill
パッケージインストール
yarn add picturefill
index.tsに記述
import picturefill from 'picturefill'
picturefill();
エラーが出るので、型をインストール
yarn add @types/picturefill
object-fit
パッケージインストール
yarn add object-fit-images
index.tsに記述
import picturefill from 'picturefill'
+ import objectFitImages from 'object-fit-images';
picturefill();
+ objectFitImages();
フォントファミリーを書く必要があるので、postcssを入れて自動化します。
yarn add postcss-object-fit-images
postcss.config.jsに設定を追加
module.exports = {
plugins: [
require('postcss-object-fit-images'),
]
}
一旦完成!
Sassの設定などはお好みで!
一案件終えてアップデート
- パスが絶対パスなので、相対パスに変更
- 余分なfile-loaderを削除
const path = require("path");
const glob = require("glob");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ImageminWebpWebpackPlugin = require("imagemin-webp-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const webpackConfig = {
entry: "./src/assets/index.ts",
output: {
path: path.resolve("dist"),
publicPath: "./",
filename: "assets/scripts.js",
},
module: {
rules: [
{
test: [/\.ts$/, /\.tsx$/, /\.js$/],
exclude: /node_modules/,
use: ["babel-loader", "ts-loader"],
},
],
},
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
alias: {
"~": path.resolve(__dirname, "./src/assets/sass/"),
imagePath: path.resolve(__dirname, "./assets/img/"),
},
},
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: "src/assets/img/",
to: path.resolve(__dirname, "dist/assets/img"),
},
],
}),
new ImageminWebpWebpackPlugin({
config: [
{
test: /\.(jpe?g|png)$/i,
options: {
quality: 60,
},
},
],
overrideExtension: false,
detailedLogs: true,
}),
],
};
glob
.sync("**/*.tsx", {
cwd: "src/pages",
})
.forEach((file) => {
const extname = path.extname(file);
const basename = path.basename(file, extname);
const dirname = path.dirname(file);
webpackConfig.plugins.push(
new HtmlWebpackPlugin({
template: path.resolve("src/pages", file),
filename: path.join(dirname, basename + ".html"),
})
);
});
module.exports = webpackConfig;
CSSの背景画像を指定するとエラーになるので、
style-loaderのoptionにurl: false,を指定
問題なく納品できたので、一旦クローズ
頑張って記事書く