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-
」部分です。 - module2
- ...
- module1 : モジュール名を指定します。Salesforce 版 LWC で言う所の「
- resources
- index.html : 一番最初に表示される HTML ファイルです。基本的に
<div id="main"></div>
位しか書いていません。 - index.js : 一番最初の画面が表示された祭に実行される JavaScript です。LWC アプリを起動する役割があります。
- modules : LWC モジュール/コンポーネントを配置します
- server : バックエンドサーバソースです。
express
で作成することになります。
- client : LWC のソース本体です
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
共通スタイルシートを適用する
デフォルトでは、何のスタイルも自動適用されません。
そこで、共通のスタイルを適用するため、次のような手段を用います。
src/styles/common.css
を作成する。src/client/modules/common/BaseElement
を作成する。(※1)- コンポーネント作成時は、この
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 をセットアップ
デフォルトでは、データベースすらセットアップされていない。
そこで、データベースを使えるようにセットアップを行う。
npm install sqlite3 -i
で sqlite3 をインストールする- 後述のような共通 DB アクセスライブラリ(
DBCommon
)を作成する ※1 - データベースアクセスクラス(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
ノートン等に「壊れたレジストリが見つかりました」と言われたときの無料対処
ノートン や McAfee 等を入れてるとたまに「壊れたレジストリが見つかりました」と言われることがある。
で、そのダイアログを見ると、追加コンポーネントの購入だとか、製品ページに飛ばされるわけである。
見つけてくれるのはありがたいが、騙しがないだけで正直「スケアウェア1」の感がある。
レジストリとは?
OS・ハードウェア・プログラムのオプション、設定、情報、その他の値を保存するデータベース。
Windows 設定とかアプリのライセンス情報とかこの辺に入ってたりする。
これが壊れると、不安定になったり、アプリ起動がおかしかったり、つながってるデバイスがうまく認識しなくなったり等が発生する。
regedit
使って、主導で編集もできなくないし、そうすることで更に細かいWindowsカスタマイズだったり効率化だったりもできなくはない。
本題からは外れるので、これは一旦横に置いとく。
修復方法
ということでその修復方法。
Avast の有料プランとか、ノートンの追加コンポーネントとかは買わなくていい。
Windows の標準で大体治せる。
手段 1 コマンドだけで治す
- cmd を管理権限で起動する。
アプリケーションの検索で「cmd」で探して、右クリックメニューに「管理者として実行」があるのでクリック。
sfc /scannow
を実行して、修復。
Windows には標準でついてるスキャナがあるのでこれを実行。
お茶でも飲んでお待ち下さい。
DISM /Online /Cleanup-Image /ScanHealth
で修正を確認して終了。
修正が完了下なら上記コマンドで修正結果を確認できる。手段 3 を状況次第で検討する。
手段 2 自動修復機能
- 設定から「システム>回復」を呼び出して、「PCの起動をカスタマイズする」で再起動
- 起動オプションで「トラブルシューティング」を選択
- 「詳細オプション」→「スタートアップ修復」を実行すると、自動修復モードに入る。
手段 3 壊れる前の状態に戻す
※ この手段は以前の状態に戻す操作なので、治りはするけど色々消えてしまうのでバックアップ取ってからを推奨。
- コンパネ開いて
- 「回復」「システムの復元を開く」
- 壊れる前を探して戻す
これでもダメなら最終手段
最終手段 : リセットする
Windows を初期化しちゃえば破損なんて全部戻るよね☆ という最強かつユーザのダメージも大きい文字通りの最終手段。
当然ライセンスキーなど含めバックアップを推奨
設定から「このPCをリセット」する。
個人用ファイルも「すべて削除する」とか、「ファイルを削除してドライブのクリーニングを実行する」までやるとほぼ確実。
PCを買ってきたばかりの頃のサクサク感をお楽しみください。
-
利用者に恐怖を与えて、お金を請求する悪質なソフト/広告等のこと↩
CakePHP 4 で特定のラジオボタン出力だけでレイアウトをフルカスタムする
通常であればフォームヘルパー経由でラジオボタンを作成するはず。
しかし、通常のフォームは以下の様な構造で出力されてしまう。
<div class="input radio"> <label for='xxx1'> <input type='radio' id='xxx1' name='xxx1' /> Hoge </label> </div>
これをどうにか
<div class="input radio"> <div class='wrapper'> <input type='radio' id='xxx1' name='xxx1' value='Hoge' /> <label for='xxx1'> Hoge </label> </div> </div>
こうしたい。
そこで、実際にこんな出力が出るように調査してみたログ。
で、目をつけたのが これ です。
で、具体的にどう書くのかをペタリ。
明日コピペできるコードをあなたに
$this->Form->control( 'xxx', // Id 値 { 'type' => 'radio', // input タイプはラジオボタン 'options' => ['Hoge', 'Fuga'], // 選択値 'nestedInput' => false, // ネストレイアウト(label の中に input タグを置く設定)を false 'templates' => [ // 出力するテンプレートの上書き // input タグの出力テンプレート上書き 'radio' => '<input type="radio" name="{{name}}" value="{{value}}"{{attrs}} />', // ラベルの出力上書き。 'nestingLabel' => '{{hidden}}{{input}}<label {{attrs}}>{{text}}</label>', // 上2つのラッパー 'radioWrapper' => '<div class='wrapper'>{{label}}</div>' ] } );
とりあえずこれで意図通りの出力ができるようになった。
というか公式が「暗黙の了解でわかるんだろう?」的な説明不足で、初見では具体的な設定方法がマジ読み取れん(汗
書籍レビュー:ジェイソン流お金のお増やし方
紹介する書籍
ざっくり書評
投資未経験者~中級者まで楽しく読める投資入門書。
(自分は上級では無いので、おそらく中級)
約15年間投資を続けたことで、FIRE を既に達成している人物による投資の経験本。
感覚的に日本人に非常に合う投資方法(リスクとしては低での投資)を行っていることもあり、コレを真似るだけで十分再現できそうな内容。
見どころ
- ジェイソン氏が金持ちになった方法≒おすすめの方法
- 達成するための価値観、考え方
- 死ぬほどやってることはシンプル(これで駄目ならどんな投資本見ても無駄!)
それ故に日本人の殆どが全てでないにせよ、それなりに貯蓄を作ることができます。
尚、海外投資で「アセットアロケーション」とか「リスク許容度」等銘柄を選ぶ上で必要な判断基準用語には触れません。
なので銘柄を自分で判断する場合の考え方…などを学ぶには少々難しいかもしれません。
とはいえ、初心者向けならそんな事せずにとりあえず「VTIにぶっこんどけ!」で良いのかもしれません。(汗
ざっくり内容
ジェイソン流の投資方法は以下の通り
これだけ!というかコレが全部と言っていいです。
具体的やり方…という目次とその概要。
各章のざっくり
尚、2章+3章で本 7 割を占めるという…
続きを読むSalesforce 開発者の JavaScript スキル(10)
Prepare for your Salesforce JavaScript Developer I Credential Trailmix
コレの続き。
Lightning Web コンポーネントと Salesforce データ
Lightning データサービスを使用したデータの操作
まぁ単純に作る系のお話。
- LWC(Name: ContactCreator) 作って
- コンポーネントではオブジェクトとフィールド名を用意。保存完了時に
toast
(画面↑に緑の帯で出るメッセージ)表示
オブジェクトのフィールド定義系
// Object import CONTACT_OBJECT from '@salesforce/schema/Contact'; // Fields import FIRST_NAME_FIELD from '@salesforce/schema/Contact.FirstName'; import LAST_NAME_FIELD from '@salesforce/schema/Contact.LastName'; import EMAIL_FIELD from '@salesforce/schema/Contact.Email'; // 中略 objectApiName = CONTACT_OBJECT; fields = [FIRST_NAME_FIELD, LAST_NAME_FIELD, EMAIL_FIELD];
- 保存成功時は toast で表示する
import { ShowToastEvent } from 'lightning/platformShowToastEvent'; // 中略 handleSuccess(event) { const toastEvent = new ShowToastEvent({ title: "Account created", message: "Record ID: " + event.detail.id, variant: "success" }); this.dispatchEvent(toastEvent); }
- HTML では
lightning-record-form
ドキュメント で設定
<lightning-record-form object-api-name={objectApiName} fields={fields} onsuccess={handleSuccess}> </lightning-record-form>
- 最後に Lightning App Builder で登録できるようにして登録
<?xml version="1.0" encoding="UTF-8"?> <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>52.0</apiVersion> <isExposed>true</isExposed> <targets> <target>lightning__AppPage</target><!-- アプリケーションページで使いますよー宣言 --> </targets> </LightningComponentBundle>
ちなみに、これで一旦レコード作ってしまうと、再編集しても保存後の表示が変わらない…
キャッシュ機構があると思われるので、
refreshValues() {
const inputFields = this.template.querySelectorAll('lightning-input-field');
if (inputFields) {
inputFields.forEach((field) => {
field.reset();
});
}
}
で強制的にリフレッシュしてもいいし、レコード状態が変化したことを Salesforce経由で通知 してもいい
続きを読むDeepLearning 生成させる学習方法
RNN(LSTM/GRU) 等
時系列のデータを扱うタイプの DeepLearning.
画像であれば、画像のY軸を時系列データと想定して、残りを予測させるといったやり方で画像を作成できるっぽい。
要するにどのように時系列データとして食わせるかで次の値を予測させるというもののようだ。
単純に手書き数字の上半分だけ食わせて、下半分を予測させるとこんな感じで生成された。
2,3,8 は上半分の形状が似ているせいでよくわからないことになってしまった。
8, 7 は学習データがそもそも悪いwww
5, 6 は混ざって学習したんじゃないかという予想。
9 は上半分で切ったとき微妙に2に混ざったかなにかしたかなという印象。
時系列情報から次の値を予測させるという意味では、やはり先頭部分の情報ありきだ。
キモは「どのように時系列のデータに見立てるか」だと思われる。
尚、翻訳などにはこれを利用した Seq2Seq (Sequence to Sequence。Sequence という系列情報を認識して別の系列に変換する) というアルゴリズムを使っていて、この根底も RNN を使っていくそうな。
ぱっと見た感じ、形態素解析した上でRNNに食わせて変換しているっぽい。
CNN
認識・判定用
これは画像などの認識を行うニューラルネット。
映像判定とか顔判定等をメインに扱うものなので、極論すると生成には向かないと思われる。
単純な入力→出力型DeepLearning
下書きを読み込んで線画にするAIは予測系ではなくて単純に入力→結果対応をしてると思う。
画像を行列にして食わせて、線画の結果を取得する。
色を付けるのも多分似たようなものかもしれない。
ただしサンプルは相当な量が必要だし、画像となれば学習時間もエグい事になりそう…
ちょっと個人で手が出るものでは無いと思われる。
VAE (Variational Autoencoder)
画像などから特徴情報に変換(Encoder。特徴抽出と圧縮)し、中間集中力(この値を「潜在変数」と呼び、)その特徴情報から出力(Decoder)の二段回の変換を行うニューラルネット。
潜在変数値は元の情報より圧縮されてる。教師なし学習に分類されるご様子。
主に、入力と出力の差分を取ることで異常値を検出できる。
で、この時潜在変数を任意にイジってやることで、全く新しい画像を自動生成できるということらしい。
派生で Conditional VAE (潜在変数の他に、ラベルパラメータを指定することで、生成するデータのカテゴリを指定できるようにしたVAE)。
β-VAE (複数要素を含む情報を処理できるVAE)Vector Quantised-VAE (潜在変数が特徴を捉えきれなかった場合にボケる等が発生する問題に対処したVAE。潜在変数を離散値で扱う) Vector Quantised-VAE2 (Vector Quantised-VAE を階層構造にすることで、高解像度の生成を行えるようにしたもの)
GAN (Generative Adversarial Networks)
敵対的生成ネットワーク。
Generator (ランダムなノイズから、偽データを生成するNN)と、Descriminator(偽データと本物のデータを見て真贋判定するNN)の2つで競わせるニューラルネットワーク。
完全に新しい画像等を生成するのに用いる。
Salesforce 開発者の JavaScript スキル(9)
Prepare for your Salesforce JavaScript Developer I Credential Trailmix の続き
また来たよ英語回
Get Started with Lightning Development
Lightning 開発を始めよう
Get Started with Lightning Development 単元 | Salesforce Trailhead
VF 使いのためのLightning入門記事ですね。
VF は 2006 年以前に、エンタープライズ・ソリューションとして作成され、サーバサイド動作として作られた。
2006 年以降、インタラクティブ、動的、レスポンシブなデザインに切り替わってきていて、Salesforce としては Lightning Experience として Lightning component フレームワークを作成した。
- コンポーネントあはSalesforce内で再利用可能
- アプリケーション機能を容易に拡張できる
- AppExchange からカスタムコンポーネントをDLして開発できる
- モバイル機器に最適化したレスポンシブデザインの開発を開発者に提供する
- ベースコンポーネントは非常に多くの機器で動作する
あと、Low-Code と Pro-Code でカテゴリ分けてる。
- Low-Code : クリックベースで開発できるやつ(フローとか、クイックアクション、URLボタンなど)
- Pro-Code : プログラミングして開発するやつ
Learn How Coding Concepts Apply to Lightning Web Components
LWC ではどのような開発コンセプトが適用されているか学習する
Learn How Coding Concepts Apply to Lightning Web Components 単元 |
まぁ見たままだよね。
サーバサイドではデータの管理だけしてて、HTML 表示などは全てクライアントで行う。
OSS の世界で言うなら、 MVVM といったところ。
- Tracking State with Properties : プロパティの更新をトラッキングする。
OSS で開発してる人なら「変数バインド」といえばいいか。VF 使いなら{! 変数 }
が{変数}
になるといえばわかるはず。 - Tracking State with Getters and Setters : Getter/Setter の状態をトラッキングする。
実質変数に近い扱いでバインドできる。
get hoge() { return this._hoge; } set hoge(v) { this._hoge = v; }
<template>{hoge}</template>
- Conditional Rendering : 状態に合わせたレンダリング(If 条件)
<template> <template if:true={areDetailsVisible}> These are the details! </template> </template>
- Rendering Lists : リストのレンダリング(イテレート)
<template> <template for:each={contacts} for:item="contact"> <p key={contact.Id}> {contact.Name} </p> </template> </template>
- イベントハンドリング
<template> <a onclick={handleButtonClick}></a> </template>
handleButtonClick(e) { window.console.log(e); // イベントの表示 }
問題の傾向
* LWC は何をきっかけに再レンダリングされる?→プロパティ変更を参照して
* コンポーネントは親コンポーネントにどうやって情報を送る?→イベントだよね