はじめに
Web ComponentはReactやVue.jsのようにHTMLの要素をコンポーネント化し、再利用できるようにする技術ですが、快適に開発するには環境構築を整える必要があり、なかなか使い始めが大変です。
この記事では、まずはWeb Componentの開発しづらいポイントを解説し、それらを解消する開発環境の構築方法を紹介します。
Web Componentの開発しづらいポイント
1. HTML、CSSを扱いにくい
まず、Web Componentの基本的な定義は以下のようになります。
// エレメントのクラスを作成
class MyElement extends HTMLElement {
constructor() {
super();
// CSS
const style = `
:host {
display: block;
border-radius: 1em;
border: 1em solid #4a6eff;
height: 100px;
width: 100px;
}
`;
// HTML
const template = document.createElement("template");
template.innerHTML = `
<style>${ style }</style>
<p></p>
<slot></slot>
`;
this.attachShadow({ mode: "open" });
this.shadowRoot?.appendChild(template.content.cloneNode(true));
}
}
// ブラウザが対応しているかチェックする
if ("customElements" in window) {
// カスタムエレメントを定義
// <fr-custom-input>が定義される
customElements.define("my-element", MyElement);
}
コードを見ればお気づきになる方もいるかも知れませんが、CSSやHTMLをJavaScript内のテキストとして書いているためエディタの補正が効かなかったり、どうしてもコードが長くなりがちなので可読性が低くなるなど、この状態ではあまり開発しやすいとは言えません。
2. TypeScriptやSCSSなどで書けない
当たり前ですが、ブラウザはTypeScriptやSCSSを読み込めないため、Web Componentを書く際はJavaScriptやCSSで書く必要があります。
環境構築
上記で書いたいくつかの問題点を解決するため、webpackを使用して開発環境を整えます。
webpackインストール
まずは、webpackをインストールします。
npm init
npm i -D webpack webpack-cli
次に、webpackのconfigファイルを作成します。ライブラリなので output
にはライブラリの設定を記述します。
export default {
entry: [
"./src/ts/index.js",
],
output: {
filename: "index.js",
clean: true,
library: {
name: "my-lib",
type: "umd",
umdNamedDefine: true,
},
},
resolve: {
modules: [
__dirname,
path.resolve(__dirname, "node_modules"),
],
extensions: [".wasm", ".ts", ".tsx", ".mjs", ".cjs", ".js", ".json"],
plugins: [],
},
module: {
rules: [],
},
optimization: {},
plugins: [],
};
HTML、CSSファイルをインポートできるようにする
次に、以下のようにHTMLにHTMLファイル、CSSファイルを読み込ませるため、loader
をインストールします。
今回の例ではSCSSを読み込めるようにしています。
npm i -D html-loader sass sass-loader
import path from "path";
import webpack from "webpack";
export default {
entry: [
"./src/ts/index.js",
],
output: {
filename: "index.js",
clean: true,
library: {
name: "my-lib",
type: "umd",
umdNamedDefine: true,
},
},
resolve: {
modules: [
__dirname,
path.resolve(__dirname, "node_modules"),
],
extensions: [".wasm", ".ts", ".tsx", ".mjs", ".cjs", ".js", ".json"],
plugins: [],
},
module: {
rules: [
{
test: /\.html$/i,
use: "html-loader",
},
{
test: /\.s[ac]ss$/i,
use: [
{
loader: "sass-loader",
options: {
sassOptions: {
outputStyle: "compressed",
}
}
}
],
type: 'asset/source',
}
],
},
optimization: {},
plugins: [],
};
これで、HTMLとCSS(SCSS)をimportすることができるようになりました。
以下のようにしてファイルを読み込みます。
<p></p>
<slot></slot>
:host {
display: block;
border-radius: 1em;
border: 1em solid #4a6eff;
height: 100px;
width: 100px;
}
import html from "./template.html";
import style from "./style.scss";
export default class CustomInput extends HTMLElement {
constructor() {
super();
const template = document.createElement("template");
template.innerHTML = `<style>${ style }</style>${ html }`;
this.attachShadow({ mode: "open" });
this.shadowRoot?.appendChild(template.content.cloneNode(true));
}
}
これで、HTMLとCSSを外部で定義し、読み込めるようになったため、可読性を向上させられました。利用する際は一つのJavaScriptファイルを読み込むだけで済むので、手軽にコンポーネントを使用できます。
TypeScriptを読み込めるようにする
TypeScriptでコンポーネントを書く場合は、HTMLとCSSをインポートできるようにしたときと同じように、loader
を追加します。
npm i -D typescript ts-loader
import path from "path";
import { TsconfigPathsPlugin } from "tsconfig-paths-webpack-plugin";
import webpack from "webpack";
export default {
entry: [
"./src/ts/index.ts",
],
output: {
filename: "index.js",
clean: true,
library: {
name: "my-lib",
type: "umd",
umdNamedDefine: true,
},
},
resolve: {
modules: [
__dirname,
path.resolve(__dirname, "node_modules"),
],
extensions: [".wasm", ".ts", ".tsx", ".mjs", ".cjs", ".js", ".json"],
plugins: [
new TsconfigPathsPlugin({
configFile: "./tsconfig.json",
}),
],
},
module: {
rules: [
{
test: /\.html$/i,
use: "html-loader",
},
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
{
test: /\.s[ac]ss$/i,
use: [
{
loader: "sass-loader",
options: {
sassOptions: {
outputStyle: "compressed",
}
}
}
],
type: 'asset/source',
}
],
},
optimization: {},
plugins: [],
};
グローバルスタイルをコンパイルする
コンポーネントとは別にグローバルスタイルを定義し、CSSファイルとして出力したい場合、webpackの設定を少し工夫する必要があります。
理由としては、webpackでCSSをコンパイルして出力したいときは MiniCssExtractPlugin
を使用しますが、単純に全てのSCSSファイルを対象とするとコンポーネント用のCSSまで出力されてしまい、コンポーネントにスタイルが当たらないからです。
解決策としては、コンポーネント用のSCSSファイル名の末尾を .module.scss
に変え、ruleを定義するときに書く test
パラメータを .module
があるかないかで使用するローダーを切り替えます。
npm i -D mini-css-extract-plugin
import path from "path";
import { TsconfigPathsPlugin } from "tsconfig-paths-webpack-plugin";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import webpack from "webpack";
export default {
entry: [
"./src/ts/index.ts",
"./src/scss/index.scss", // グローバルスタイルエントリーファイル
],
output: {
filename: "index.js",
clean: true,
library: {
name: "my-lib",
type: "umd",
umdNamedDefine: true,
},
},
resolve: {
modules: [
__dirname,
path.resolve(__dirname, "node_modules"),
],
extensions: [".wasm", ".ts", ".tsx", ".mjs", ".cjs", ".js", ".json"],
plugins: [],
},
module: {
rules: [
{
test: /\.html$/i,
use: "html-loader",
},
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
{
test: /(?<!\.module)\.s[ac]ss$/i, //グローバルスタイル
use: [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "sass-loader",
options: {
sassOptions: {
outputStyle: "compressed",
},
},
},
],
sideEffects: true,
},
{
test: /\.module\.s[ac]ss$/i, // コンポーネント用スタイル
use: [
{
loader: "sass-loader",
options: {
sassOptions: {
outputStyle: "compressed",
}
}
}
],
type: 'asset/source',
}
],
},
optimization: {},
plugins: [
new MiniCssExtractPlugin({
filename: "style.css",
}),
],
};
これで、ファイル名の末尾が.module.scss
の場合はコンポーネント用のスタイルとして読み込まれてJavaScriptファイルにインライン要素として出力され、.scss
の場合はグローバルSCSSとしてCSSファイルとして出力されます。
まとめ
本記事では、Web Componentを開発する際のやりづらいポイントを紹介し、それらをwebpackを使用して解消しました。
webpackは機能が多く、自分も環境構築の際は知らなかったことが多くて苦労しましたが、webpackの機能をよく知れたいい機会だったなと思います。
本記事が読者の皆様の参考になれば幸いです。
新しいメンバーを募集しています
Hirayama / Engineer
1997年生まれ、南伊豆出身。学生時代にC#で画像処理アプリケーションを作ったりしていました。業務では主にLaravelを使用してサーバーサイドのプログラミングをしています。趣味はドライブとシミュレーションゲーム。