OSS ç LWC ã§WebApp
LWC ã¨ããã¨ãSalesforce ã® Lightning Web Component ãçã£å
ã«æµ®ãã¶äººã¯ãSalesforce çéã®äººã ã¨æãã
ã§ãSalesforce ãã©ãããã©ã¼ã ä¸ã®ãã¬ã¼ã ã¯ã¼ã¯ã¨ãã¦æåã§ã¯ããã®ã ãã©ãå®éã«ã¯ããã¤ã¯ Opensource ã¨ãã¦ã使ããã
å ã¿ã«ãã®è¨äºå·çå½æã® nodejs ã®ãã¼ã¸ã§ã³ã¯
$ node --version v14.17.1 $ npm --version 7.17.0
å§ãæ¹
ã¨è¨ã£ã¦ããããªã«é£ãã話ã§ã¯ãªãã
nodejs ãå
¥ã£ã¦ããªãã大ä½ãã®ã³ãã³ãã§è¡ããã
$ 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 (Use arrow keys) > Standard web app Progressive Web App (PWA) Electron app
å½ç¶æ¨æº WebApp ã鏿
? Do you want a basic Express API server? (y/N) y
express API ãµã¼ãã¯ãããã¯ã¨ã³ãã§åä½ãããWebãµã¼ãã®äºã
https://white-azalea.hatenablog.jp/entry/2021/07/14/220816
ã§ã使ããã²ãªåããããªæã
éçºãµã¼ããèµ·å
$ npm run watch
ã¨å©ãã¨ãéçºç¨ã®Webãµã¼ã (LWCç¨)ã 3001 ãã¼ãã§ãAPI ãµã¼ãã 3002 ãã¼ãã§èµ·åããã
Web ãµã¼ãï¼ãã¼ã 3001ï¼ã«ã¢ã¯ã»ã¹ãã¦åå¨ããªããªã¯ã¨ã¹ããªãAPIãµã¼ãã«ã«ã¼ãã£ã³ã°ãããããè¨å®ããã¦ããã®ã§ãAPI ãµã¼ãã®æ¹ã®ã½ã¼ã¹(src/api.js
)ã§ä»¥ä¸ã®æ§ã«æ¸ããã¦ãããªã
// Simple Express server setup to serve for local testing/dev API server const compression = require('compression'); const helmet = require('helmet'); const express = require('express'); const app = express(); app.use(helmet()); app.use(compression()); const HOST = process.env.API_HOST || 'localhost'; const PORT = process.env.API_PORT || 3002; app.get('/api/v1/endpoint', (req, res) => { res.json({ success: true }); }); app.listen(PORT, () => console.log( `â API Server started: http://${HOST}:${PORT}/api/v1/endpoint` ) );
ãã¼ã 3002 ã¯ãã¡ãã
ãã¼ã 3001 ã§ã
ã¨ã§ããã
JSON ãµã¼ãã«ä»ç«ã¦ãã
src/server/api.js
ã®å®ç¾©ã§ã¯ãæ¨æºã§ã¯ application/x-www-form-urulencoded
ã§éåä¿¡ã£ãããã¨ãæå¾
ãã¦ããã
ããã§ããµã¼ãè¨å®ã§ãåºæ¬çã« application/json
ã¤ã¾ããJSON å¤ãåãåãåæã«å¤æ´ããã
ä½ã®ãã¨ã¯ãªã app.use(express.json());
ã追å ããã ãã ã
const app = express(); app.use(helmet()); app.use(compression()); app.use(express.json()); // ãã®è¡è¿½å
ããããã¨åãåã£ãå¤ã¯ JSON ãã¼ã¹ã試ã¿ããã¦ãå®è£ ããããªæãã§æ¸ããã
app.post('/api/test', (req, res) => { let jsonBody = req.body; console.log(jsonBody); res.status(200).json(jsonBody); });
SQLITE3 çã®ãã¼ã¿ãã¼ã¹ãç¨æããã
幸ããsqlite3 ã®ããã±ã¼ã¸ãããã®ã§ npm i -S sqlite3
ã§ã¤ã³ã¹ãã¼ã«ã
使ã£ã¦ãå³ã¯ãããªæãã§ã
DBCommon.js
ã§ sqlite ãã»ããã¢ãã
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;
brands.js
ããã¼ãã«å®ç¾©ãªã©ã
const common = require("./DBCommon") const TABLE_NAME = 'brand'; const DBCommon = common.DBCommon; function createTable() { // ãã¼ãã«ãç¡ããã°ä½ãå¦ç const db = DBCommon.get(); return new Promise((resolve, reject) => { try { db.serialize(() => { db.run(`create table if not exists ${TABLE_NAME} ( id integer primary key AUTOINCREMENT, name text not null unique, description text default null )`) }); return resolve() } catch (error) { return reject(error); } }); } createTable(); class Brand { // js ã«åã¯ããç¨æå³ãªããã©ãæ°åã㪠constructor(id, name, description) { this.id = id; this.name = name; this.description = description; } } class BrandTable { /** * @param {Brand} brand */ static async insert(brand) { return new Promise((resolve, reject) => { const db = DBCommon.get(); try { db.run( `insert into ${TABLE_NAME} (name, description) values ($name, $description)`, brand.name, brand.description ) return resolve() } catch (error) { return reject(error) } }); } static async selectAll() { return new Promise((resolve, reject) => { const db = DBCommon.get(); try { const result = []; db.serialize(() => { db.all( `select id, name, description from ${TABLE_NAME} order by id`, (err, res) => { if (err) return reject(err); if (!res) return resolve([]); res.forEach(row => { result.push(new Brand(row['id'], row['name'], row['description'])) }); return resolve(result); } ) }) } catch (error) { reject(error); } }) } } exports.Brand = Brand; exports.BrandTable = BrandTable;
ããã§ã src/server/api.js
ã§ãããªé¢æ°ã追å ãã
const brands = require('./database/brands'); const BrandTable = brands.BrandTable; app.get('/api/brands', async (req, res) => { try { res.json(await BrandTable.selectAll()); } catch (error) { res.status(500).json({ success: false, message: error}); } }); app.post('/api/brands', async (req, res) => { try { await BrandTable.insert(req.body); res.json({ success: true }); } catch (error) { res.json({ success: false, message: 'Has error!' }); } });
ããã¨
LWC ã®ä½ãæ¹
ãã£ããå ¨ä½å
LWC ã¯ååçã« src/client/modules
ã®ä¸ã« 2 é層以ä¸ãã£ã¬ã¯ããªæã£ã¦ãcss
, js
, html
ã®3ç¹ã»ãããç½®ãã°ããã
ã¨ã¯ããã¨ããããã¯å
±éæ©è½ãç¨æããã
å ±éã¹ã¿ã¤ã«(Bootstrap)ãèªã¿è¾¼ãã³ã³ããã¼ã©ãAjax ã³ãã³ãç¨ãã¼ã«ãJSON API ã®ã¨ã³ããã¤ã³ãè¨å®ãå®ç¾©ãã¨ã
import { LightningElement } from 'lwc'; export default class CssCommonElement extends LightningElement { _bootStrapCss() { let _bootstrap = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css'; const styles = document.createElement('link'); styles.href = _bootstrap; styles.crossorigin = 'anonymous'; styles.rel = 'stylesheet'; return styles; } connectedCallback() { this.template.appendChild(this._bootStrapCss()); } } export class Ajax { /** * get as promise * @param {string} url * @returns {Promise} */ static get(url) { return new Promise((resolve, reject) => { const req = new XMLHttpRequest(); req.open('GET', url, true); req.onload = () => { if (req.status >= 200 && req.status < 300) { resolve(JSON.parse(req.responseText)); } else { console.log(`Error ocurred ${req.status} : ${req.responseText}`); reject(new Error(req.statusText)); } }; req.onerror = () => { reject(new Error(req.statusText)) }; req.send(); }); } } export class Endpoints { static GET_ALL_BRANDS = '/api/brands'; }
ã§ãapp.js
import CssCommonElement from '../../lib/common/common'; export default class App extends CssCommonElement {}
app.html
ãæ¬¡ã®æ§ã«è¨å®ãã¾ã
<template> <div class="container-fluid"> <div class="row"> <div class="col-md-4"> <button class="btn btn-primary">Add new brand</button> </div> <div class="col-md-8 test">Right component</div> </div> </div> </template>
ããã¾ã§æ¸ãã°ãindex.js
ãä¿®æ£ãã¾ãã
import { createElement } from 'lwc'; import EntryApp from 'book/app'; const app = createElement('account-book', { is: EntryApp }); // eslint-disable-next-line @lwc/lwc/no-document-query document.querySelector('#main').appendChild(app);
è¦ã¦ã®éããmodules
以ä¸ã¯ãã®ã¾ã¾å¼ã³åºãã¦ã¾ãã
ããã§ã¢ã¯ã»ã¹ããã¨
ããã§ã追å ã®ã³ã³ãã¼ãã³ããå®ç¾©ãã¾ã
book/brandSelector/brandSelector.html
<template> <template if:true={isLoading}> <h1>èªè¾¼ä¸</h1> </template> <template if:false={isLoading}> <template if:true={hasError}><h1>{errorMessage}</h1></template> <template if:false={hasError}> <div class="list-group"> <template for:each={allBrands} for:item="brand" for:index="idx"> <template if:true={brand.isSelected}> <a href="#" class="list-group-item list-group-item-action active" key={brand.id} title={brand.description} onclick={onSelect} data-id={brand.id}>{brand.name}</a> </template> <template if:false={brand.isSelected}> <a href="#" class="list-group-item list-group-item-action" key={brand.id} title={brand.description} onclick={onSelect} data-id={brand.id}>{brand.name}</a> </template> </template> </div> </template> </template> </template>
book/brandSelector/brandSelector.js
import CssCommonElement, { Ajax, Endpoints } from '../../lib/common/common'; import { track } from 'lwc'; export default class BrandSelector extends CssCommonElement { @track allBrands; @track hasError; @track errorMessage; @track isLoading; constructor() { super(); this.allBrands = []; this.hasError = false; this.errorMessage = ''; this.isLoading = true; this.loadBrands(); } async loadBrands() { try { let brands = await Ajax.get(Endpoints.GET_ALL_BRANDS); brands.forEach(v => { v.isSelected = false; }); this.allBrands = brands; this.isLoading = false; } catch (error) { this.hasError = true; this.errorMessage = 'éæã®èªã¿è¾¼ã¿ã«å¤±æãã¾ãããç»é¢ããªãã¼ããã¦ãã ããã'; this.isLoading = false; } } onSelect(event) { let target = event.path[0]; let id = target.getAttribute('data-id'); this.allBrands.forEach(v => { v.isSelected = v.id == id; }); this.dispatchEvent(new CustomEvent('select', { detail: id })); } }
ã§ app,html
ããå¼ã³åºãã¦ã¿ã¾ãã
<book-brand-selector ></book-brand-selector>
SQLite ã®ä¸èº«ã¯ãããªæãã
ã¨ãããã¨ã§ä»æ¥ã¯ãã®è¾ºã¾ã§