ランサーズ(Lancers)エンジニアブログ > フロントエンド > フロントエンド定例 > Web Components Litのプロジェクト構成 Material Design 3とmaterial-web フロントエンド定例 2023/2/10

Web Components Litのプロジェクト構成 Material Design 3とmaterial-web フロントエンド定例 2023/2/10

blog_admin|2023年02月10日
フロントエンド

こんにちは、フロントエンドチームの @syo_igarashi です。
今週のフロントエンド定例の内容を記載します。

フロントエンド定例について、以前の記事(ランサーズのフロントエンドチームが取り組んでいること)でお伝えしたのですが、毎週金曜日に開催しており、実際の業務で取り組んでいることや気になった技術情報等をシェアしあう会になっています。

以下、今週の内容です。

Web Components Litのプロジェクト構成

ビルドの設定は前回にも記載してたviteコマンドで作成したものとほぼほぼ同じなので一部説明割愛します

スライドツールreveal.js Web ComponentsライブラリのLit知見 フロントエンド定例 2023/2/3

ディレクトリ構成

  • lit_web_components
    • .storybook
      • main.cjs
        • ../packages/*/src/**/*.stories.ts を読み込むような設定にする
    • packages
      • LitDesignSystem
        • src
          • components
            • LComponent
            • LHoge
          • index.ts
        • dist
          • umd.js
          • es.js
        • package.json
        • tsconfig.json
        • vite.config.ts
      • XxxxYyyy
        • src
          • stories
          • index.ts
        • dist
          • umd.js
          • es.js
        • package.json
        • tsconfig.json
        • vite.config.ts
    • .eslintrc.cjs
    • .prettierrc.js
    • custom-element.vscode.json
    • package.json
    • pnpm-lock.yaml
    • pnpm-workspace.yaml
    • project_generator.ts
    • vite.config.ts

というような構成にしました

pnpm-workspaceによる依存ライブラリの共有化してたり、
Storybook、ESLint, Prettierrなど共通で使用したいものはlit_web_components直下に置くようにし、packagesのディレクトリ内の操作でも

pnpm -w storybook
pnpm -w lint

で確認可能な環境にしてます。

XxxxYyyyディレクトリはテンプレートのようなプロジェクトで設置しておりproject_generator.ts
でプロジェクトの複製を行えるようなコマンドを置いてたりします。

node --require esbuild-register project_generator.ts project=HogeFuga

コードの内容

import fs from ‘fs’;
import fse from ‘fs-extra’;
import path from ‘path’;

export const main = () => {
  const colors = {
    red: ‘\u001b[31m’,
    green: ‘\u001b[32m’,
  };

  const projectKeyValue = process.argv.join().match(/project=\S*/);
  if (!(projectKeyValue && projectKeyValue.length)) {
    console.error(`${colors.red}
not found projectKeyValue
LitによるCustum Tag実装の都合上xxxx-yyyy-zzzzというケバブケースにした名称にしたいのでパスカルケースな名称を入力してください

example)
pnpm generate project=XxxxYyyyZzzz
`);
    return 1;
  }

  const projectName = projectKeyValue[0].replace(‘project=‘, ‘’);

  if (!projectName) {
    console.error(`${colors.red}
not found projectName
LitによるCustum Tag実装の都合上xxxx-yyyy-zzzzというケバブケースにした名称にしたいのでパスカルケースな名称を入力してください

example)
pnpm generate project=XxxxYyyyZzzz
`);
    return 1;
  }

  const toKebabCase = (str) => {
    if (typeof str !== ‘string’) return str;

    str = str.replace(/^ *?[A-Z]/, function (allStr) {
      return allStr.toLowerCase();
    });
    str = str.replace(/_/g, ‘-’);
    str = str.replace(/ *?[A-Z]/g, function (allStr, i) {
      return ‘-’ + allStr.replace(/ /g, ‘’).toLowerCase();
    });
    return str;
  };

  const customTagName = toKebabCase(projectName);

  const newProjectFullPath = path.resolve(
    __dirname,
    `./packages/${projectName}`
  );
  const xxxxYyyyProjectFullPath = path.resolve(
    __dirname,
    `./packages/XxxxYyyy`
  );

  fs.rmSync(newProjectFullPath, { recursive: true, force: true });
  fse.copySync(xxxxYyyyProjectFullPath, newProjectFullPath);

  console.info(`${colors.green}
  copy ${newProjectFullPath}
`);

const packageJSONPath = path.resolve(newProjectFullPath, ‘./package.json’);
const viteConfigPath = path.resolve(newProjectFullPath, ‘./vite.config.ts’);
const srcIndexPath = path.resolve(newProjectFullPath, ‘./src/index.ts’);
const srcXxxxYyyyPath = path.resolve(
  newProjectFullPath,
  ‘./src/xxxx-yyyy.ts’
);
const srcStoriesPath = path.resolve(
  newProjectFullPath,
  ‘./src/stories/default.stories.ts’
);

fs.writeFileSync(
  packageJSONPath,
  fs
  .readFileSync(packageJSONPath)
  .toString()
  .replace(/XxxxYyyy/g, projectName)
);

fs.writeFileSync(
  viteConfigPath,
  fs
  .readFileSync(viteConfigPath)
  .toString()
  .replace(/XxxxYyyy/g, projectName)
);

fs.writeFileSync(
  srcIndexPath,
  fs
  .readFileSync(srcIndexPath)
  .toString()
  .replace(/xxxx-yyyy/g, customTagName)
);

fs.writeFileSync(
  `${newProjectFullPath}/src/${customTagName}.ts`,
  fs
  .readFileSync(srcXxxxYyyyPath)
  .toString()
  .replace(/xxxx-yyyy/g, customTagName)
  .replace(/XxxxYyyy/g, projectName)
);
fs.rmSync(srcXxxxYyyyPath);

fs.writeFileSync(
  srcStoriesPath,
  fs
  .readFileSync(srcStoriesPath)
  .toString()
  .replace(/xxxx-yyyy/g, customTagName)
  .replace(/XxxxYyyy/g, projectName)
);

console.info(`${colors.green}
Success.
`);

return 0;
};

if (process.execArgv.includes(‘esbuild-register’)) {
  try {
    const status = main();
    process.exit(status);
  } catch (e) {
    console.error(e);
    process.exit(1);
  }
}

みたいなコードでプロジェクトの複製を行なっています。

それぞれのWeb ComponentsのJavaScriptの読み込みとCustom Elementsを使用する

vite buildを実行するとdistディレクトリにビルドファイルが設置されるのでHTML上で

<script src="https://~~~~~~/dist/umd.js"></script>

でWeb ComponentsのJavaScriptファイルを読み込ませることで

<l-hoge></l-hoge>

と実装されているCustom Elemetsを指定すると展開されるようになります

LitDesignSystemのWebComponentsはどこでも使えるような感じにするのでそれぞれのプロジェクトのWeb Componentsの実装でも使用できるような感じにしてます。

(umd.jsを分けて出力するので必要に応じて読み込みが可能な感じにしてる)

 

Material Design 3とmaterial-web

https://m3.material.io/develop/web

Material Design 3に対応しているWeb Componentsが実は存在していることに最近気づいてみてました。

ReactならMUI, VueならVuetifyみたいな感じに各々独自のUIライブラリを作ってて(スタイルの定義がCSS in JSで作っているからという意味合いで)、昔よく使ってたBootstrapはCSSがあるからどのフレームワークでも使えて、Material DesignもCSSを公開しててそれぞれのUIライブラリが同じCSSを参照しているわけではないんだなというのを見ていて思いました。

Material Design 3(Material You)の対応進捗ってどうなんだろうかと気になってたりします。

 

前回の定例内容はこちらから確認可能ですのでご興味いただければ下記のリンクから閲覧いただければと思います。

https://engineer.blog.lancers.jp/?s=フロントエンド定例