技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

Salesforce 以外の LWC

目次

プロジェクトの作成方法

ざっくりアプリケーションの雛形を作成します。
※ このやり方は 2022/03 としてはやや古いやり方です。最新版は、公式レシピ含め、Windows11 で正常に動作しなかったため、説明を諦めます。

$ npx create-lwc-app account-book

⚡⚡⚡⚡⚡  Lightning Web Components  ⚡⚡⚡⚡⚡
? Do you want to use the simple setup? Yes
? Package name for npm account-book
? Select the type of app you want to create Standard web app
? Do you want a basic Express API server? Yes
   create package.json
   create .eslintrc.json
   create .eslintignore
   create .prettierignore
   create .prettierrc
   create .gitignore
   create .husky\pre-commit
   create src\client\modules\jsconfig.json
   create lwc.config.json
   create lwc-services.config.js
   create jest.config.js
   create README.md
   create src\client\index.html
   create src\client\index.js
   create src\client\modules\my\app\app.css
   create src\client\modules\my\app\app.js
   create src\client\modules\my\app\app.html
   create src\client\modules\my\greeting\greeting.css
   create src\client\modules\my\greeting\greeting.js
   create src\client\modules\my\greeting\greeting.html
   create src\client\modules\my\app\__tests__\app.test.js
   create src\client\modules\my\greeting\__tests__\greeting.test.js
   create src\client\resources\lwc.png
   create src\client\resources\favicon.ico
   create scripts\server.js
   create src\server\api.js

プロジェクトディレクトリが作成されたので

$ cd account-book
$ npm run watch

これで localhost の 3001 ポートでサーバが起動します。
http://localhost:3001/

ディレクトリ構成

ディレクトリ構成は、以下のようになっています。
通常のLWCが Salesforce 前提ですが、OSS 版は Salesforce は無いので、サーバはほぼ自作する必要があります。

  • プロジェクトルート
    • scripts : 開発サーバ設定が記載されています(基本的に触りません)
    • src : ソースディレクトリルート
      • client : LWC のソース本体です
        • modules : LWC モジュール/コンポーネントを配置します
          • module1 : モジュール名を指定します。Salesforce 版 LWC で言う所の「c-」部分です。
            • component1 : LWC コンポ-ネントディレクトリです。
              • component1.html : テンプレート
              • component1.css : スタイル
              • component1.js : コントローラソース
            • component2
            • ...
          • module2
          • ...
        • resources
        • index.html : 一番最初に表示される HTML ファイルです。基本的に <div id="main"></div> 位しか書いていません。
        • index.js : 一番最初の画面が表示された祭に実行される JavaScript です。LWC アプリを起動する役割があります。
      • server : バックエンドサーバソースです。express で作成することになります。

LWC アプリ開発の違い

  • メタデータが存在しない Salesforce 版だと、LWCをどこに使うか等を指定するメタデータがありますが、OSS 版はどこかに埋め込む運用ではないので、そんなものはありません。
    制御は全部 JavaScript/JSON で記述します。
  • バックエンドが存在しない Salesforce ではないので、バックエンドは存在しません。
    代わりに、ExpressJS がセットアップされるので、これをつかって API サーバを作成し、フロントの LWC 側から Ajax 通信で処理します。※ Tips に関連
  • JavaScript への制限がない
    Salesforce 版だと、JavaScript の記述には一部制限が入ります。(おそらく好き勝手されないように)
    しかし、OSS 版はそうした制限はないので、adapter やプロトタイプ汚染等、そこそこ凶悪な操作がやりたい放題です。
  • モジュール名が自由に命名できる
    Salesforce 版だと、カスタムコンポーネントc- プレフィックス固定で、コンポーネント名も小文字のハイフン区切り(例 : <c-example-component />)すが、 OSS 版はその制限がないので <bootstrap-customAlert /> といったそれなりに自由なコンポーネント名で定義できます。
  • 共通スタイルは無い
    Salesforce 版だと、Lightning Design System のスタイルが自動適用されますが、OSS 版はそれはありません。
    自力で適用する必要があります。※ Tips に関連

Tips

共通スタイルシートを適用する

デフォルトでは、何のスタイルも自動適用されません。
そこで、共通のスタイルを適用するため、次のような手段を用います。

  1. src/styles/common.css を作成する。
  2. src/client/modules/common/BaseElement を作成する。(※1)
  3. コンポーネント作成時は、この BaseElement を継承する。(※2)

※1

import { LightningElement } from 'lwc';
export default class CssCommonElement extends LightningElement {
    _commonCss() {
        let _bootstrap = '../styles/common.css';
        const styles = document.createElement('link');
        styles.href = _bootstrap;
        styles.rel = 'stylesheet';
        return styles;
    }

    connectedCallback() {
        this.template.appendChild(this._commonCss());
    }
}

※2

import BaseElement from 'common/basBaseElemente';

export default class AddBrand extends BaseElement { /* ... */ }

バックエンドサーバ(Express)で REST API を作成する

デフォルトでは、Express JS サーバは REST API を作成する形ではセットアップされない。
そこで、REST API モードとするために設定を行う

これは src/server/api.js で次のように記述する。

const express = require('express');

const app = express();
app.use(express.json());

NodeJs で SQLite3 をセットアップ

デフォルトでは、データベースすらセットアップされていない。
そこで、データベースを使えるようにセットアップを行う。

  1. npm install sqlite3 -i で sqlite3 をインストールする
  2. 後述のような共通 DB アクセスライブラリ(DBCommon)を作成する ※1
  3. データベースアクセスクラス(DAO)を作成していく ※2

こんな感じにしておけば色々使いやすいかも知れない。

※1

const sqlite3 = require("sqlite3")

let database

class DBCommon {
  static init() {
    database = new sqlite3.Database("data.sqlite3")
  }
  static get() {
    return database
  }
}

DBCommon.init();
exports.DBCommon = DBCommon;

※2

const common = require("./DBCommon");
const sqlite3 = require("sqlite3")

function createTable() {
  const db = common.DBCommon.get();
  return new Promise((resolve, reject) => {
    try {
      db.serialize(() => {
        db.run(
          `create table if not exists Brand (
            id integer primary key AUTOINCREMENT,
            name varchar(512) not null,
            account varchar(32) not null,
            description text default null
          )`
        )
      });
      return resolve();
    } catch (error) {
      return reject(error);
    }
  });
}
createTable();

class BrandTable {
  static async select(id, db = common.DBCommon.get()) {
    return new Promise((resolve, reject) => {
      db.get(`select id, name, account, description from Brand where id = ?`, id, (err, row) => {
        if (row) {
          resolve(row);
        } else {
          reject(err);
        }
      });
    });
  }

  static async insert(brand, db = common.DBCommon.get()) {
    db.run(
      `insert into Brand (name, account, description) values ($name, $account, $description)`,
      { $name: brand.name, $account: brand.account, $description: brand.description }
    );
  }
}
exports.BrandTable = BrandTable;

通信用アダプタ

こんな通信用クラスを用意しておくと便利です。

function jsonSending(method, url, sendBody) {
  return new Promise((resolve, reject) => {
    let req = new XMLHttpRequest();
    req.open(method, url);
    req.onload = function() {
      if (req.status === 200) {
        resolve(JSON.parse(req.responseText));
      } else {
        window.console.log(req);
        reject(new Error(req.statusText));
      }
    };
    req.onerror = function() {
      window.console.log(req);
      reject(new Error(req.statusText));
    };
    req.setRequestHeader('Content-Type', 'application/json');
    req.send(JSON.stringify(sendBody));
  });
}

export default class Ajax {

  /**
   * HTTP Get request
   * @param {String} url
   * @returns Promise(String)
   */
  static async get(url) {
    return new Promise((resolve, reject) => {
      let req = new XMLHttpRequest();
      req.open('GET', url, true);
      req.onload = function() {
        if (req.status === 200) {
          resolve(JSON.parse(req.responseText));
        } else {
          reject(new Error(req.statusText));
        }
      };
      req.onerror = function() {
        reject(new Error(req.statusText));
      };
      req.send();
    });
  }

  /**
   * HTTP Post request.
   * @param {String} url
   * @param {String} sendBody
   * @returns Promise(String)
   */
  static async post(url, sendBody) {
    return jsonSending('POST', url, sendBody);
  }

  /**
   * HTTP PUT request.
   * @param {String} url
   * @param {String} sendBody
   * @returns Promise(String)
   */
   static async put(url, sendBody) {
    return jsonSending('PUT', url, sendBody);
  }
}

参考情報

-ExpressJs : https://expressjs.com/ja/ 軽量な Javascript Web フレームワーク - LWC : https://lwc.dev/ - テンプレートエンジン : https://developer.salesforce.com/docs/platform/lwr/guide/lwr-templates.html