Yazıma başlarken öncelikle şu sorunun cevabını arayalım, widget nedir?

Widget, orijinal uygulamanın minyatür bir versiyonu olan herhangi bir uygulamadır. İstediğiniz herhangi bir uygulamadan bir widget oluşturabilirsiniz.

Widget'leri genellikle mobil cihazlarda ve bilgisayarlarda küçük ve sürüklenebilir pencereler olarak görmüş olabilirsiniz. Örneğin, bir uygulamanız var, en sevdiğiniz müzik uygulamanızın küçük bir kayan widget'ı. Bu widget yalnızca diğer widget'lar için yer açmakla kalmaz, aynı zamanda size tam gelişmiş uygulamanın minimum bir sürümüne erişim sağlar.

Widget'lar temelde uygulamayla etkileşimde harcanan çabayı azaltır. Kullanım durumlarından biri, tüm "salt okunur" verilerin pencere öğesinde görüntülendiği ve uygulama üzerinde değiştirme veya yazma eylemlerinin gerçekleştirildiği bir "yalnızca görüntüleme pencere öğesi" olabilir. Bu şekilde, kullanıcınıza kullanımı daha kolay olan daha inceltilmiş bir sürüm sunabilirsiniz.

Şimdiz sizlerle beraber Creat, List, Update özellikli bir widget uygulamalası yapalım. 

Bunun yapmak için gerekli olanlar; 

  • UI Çerçevesi olarak React .
  • Kodlama dili olarak Typescript .
  • Tasarım için Boostrap .
  • Uygulamayı yapılandırmak ve oluşturmak için WebPack .
  • Veri depolama için Local Store.

Önce bir React uygulaması oluşturalım. Bu makale için ben buradaki  betiği kullanacağım.

Buradaki şablonu klonlayacağız ve widget'ın kodunu değiştireceğiz. Widget'ımız bir iframe içinde çalışacağından, react-router'ları kullanamayacağız. Bu nedenle, bizim durumumuzda, bileşenleri bir sayfa değişkenine göre oluşturmak için anahtar durumlarını kullanarak koşullu oluşturmayı kullanacağız.

Şablonu klonladıktan ve paketleri kurduktan sonra, widget'ımızın başlatması için bir giriş noktası oluşturmaya başlayalım. Src klasörü altında widget.ts adında bir dosya oluşturalım . Bu dosya, iframe'i ayarlamak ve oluşturmak için tüm yapılandırmayı içerecektir.

Yani, temelde 2 şeyin birleşiminden ibarettir. Widget.ts tarafından bir iframe içinde çalıştırılacak ve her yere takılabilecek normal react uygulamanız var. Props ile window ve iframe arasında doğrudan iletişim kuramayacağımızı bildiğimizden, iframe ile pencere arasında konuşmak ve props veya değerleri değiştirmek için postMessage fonksiyonunu kullanmamız gerekir.

Bunların hepsi başlangıçta kafa karıştırıcı gelebilir, ancak adım adım ilerledikçe daha kolay hale gelecektir.

Şimdi widget.ts dosyasına kod eklemeye başlayabiliriz . İlk olarak, widget'ı kullanacak olan web sayfasından yapılandırmak ve başlatmak için kullanılacak widget nesnemizi oluşturacağız. Basit bir şey yapalım.

widget.ts

 
const defaultStyles: any = {
 'border': 'none',
 'z-index': 2147483647,
 'height': '650px',
 'width': '350px',
 'display': 'block !important',
 'visibility': 'visible',
 'background': 'none transparent',
 'opacity': 1,
 'pointer-events': 'auto',
 'touch-action': 'auto',
 'position': 'fixed',
 'right': '20px',
 'bottom': '20px',
}

interface IConfig {
 readonly email: string;
}

interface IWidget {
 config: IConfig | null;
 iframe: HTMLIFrameElement | null;
 init: (config: IConfig) => void;
 setupListeners: () => void;
 createIframe: () => void;
 handleMessage: (event: MessageEvent) => void;
}

const Widget: IWidget = {
 iframe: null,
 config: null,
 init: function(config: IConfig) {
   this.config = config;
   this.createIframe()
 },
 createIframe: function() {
   this.iframe = document.createElement('iframe');
   let styles = '';
   for (let key in defaultStyles) { styles += key + ': ' + defaultStyles[key] + ';' }
   this.iframe.setAttribute('style', styles)
   this.iframe.src = 'http://localhost:9000';
   this.iframe.referrerPolicy = 'origin';
   document.body.appendChild(this.iframe);
   this.setupListeners();
 },
 setupListeners: function() {
   window.addEventListener('message', this.handleMessage.bind(this));
 },
 handleMessage: function(e) {
   e.preventDefault();
   if (!e.data || (typeof e.data !== 'string')) return;
   let data = JSON.parse(e.data);
   switch (data.action) {
     case 'init': {
       if (this.iframe) {
         this.iframe.contentWindow.postMessage(JSON.stringify(this.config), '*');
       }
       break;
     }
     default:
       break;
   }
 }
};

export default Widget;

HandleMessage işlevi, verileri hem iframe hem de üst öğe üzerinden geçirmek için React uygulamasıyla iletişim kurmak için kullanılacaktır. Bu yüzden burada, widget'ı kullanan web sayfasındaki komut dosyası etiketinde iletilen yapılandırmayı alacağız ve bunu yapılandırma değişkeninde React uygulamasına ileteceğiz. Burada iframe src'nin http://localhost:9000 olduğunu görüyoruz Bu bizim React uygulama sunucumuz olacak. Şimdi widget'ı bir sayfaya yüklemek için, önce web paketi dosyamızı farklı bir şekilde yapılandırmamız gerekiyor.

webpack.config.js

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');

const isProd = process.env.NODE_ENV === 'production';

const config = {
 mode: isProd ? 'production' : 'development',
 entry: {
   app: [
     'webpack-dev-server/client?http://0.0.0.0:9000/',
     'webpack/hot/only-dev-server',
     './src/index.tsx'
   ],
   Widget: ['./src/widget.ts']
 },
 output: {
   filename: '[name].js',
   path: resolve(__dirname, 'dist'),
   library: '[name]',
   libraryTarget: 'umd',
   libraryExport: 'default'
 },
 resolve: {
   extensions: ['.js', '.jsx', '.ts', '.tsx'],
 },
 module: {
   rules: [
     {
       test: /.tsx?$/,
       use: 'babel-loader',
       exclude: /node_modules/,
     },
     {
       test: /.css?$/,
       use: [
         'style-loader',
         { loader: 'css-loader', options: { importLoaders: 1 } },
         'postcss-loader'
       ]
     },
   ],
 },
 plugins: [
   new HtmlWebpackPlugin({
     template: './src/index.html',
     hash: true,
     filename: 'index.html',
     inject: 'body',
     excludeChunks: ['widget']
   }),
 ],
};

if (isProd) {
 config.optimization = {
   minimizer: [new TerserWebpackPlugin(),],
 };
} else {
 config.devServer = {
   port: 9000,
   open: true,
   hot: true,
   compress: true,
   stats: 'errors-only',
   overlay: true,
 };
}

module.exports = config;

Bu işlemlerden sonra artık sunucumuzu ayağa kaldırmaya hazırız. 

npm run dev

index.tsx

import React from 'react';
import { render } from 'react-dom';
import App from './App';
import { IConfig } from './config/interfaces';
import { Context } from './context/context';
import './stylesheets/index.css';

window.addEventListener('DOMContentLoaded', (event) => {
 window.parent.postMessage(JSON.stringify({ action: 'init' }), '*');
 window.removeEventListener('DOMContentLoaded', () => null);
});

window.addEventListener('message', (event) => {
 event.preventDefault();
 if (!event.data || (typeof event.data !== 'string')) return;
 const config: IConfig = JSON.parse(event.data);
 return render(
   <Context.Provider value={JSON.stringify(config)}>
     <App />
   </Context.Provider>,
   document.body
 );
});

DOM yüklendikten sonra, iframe'e react uygulamasının DOM'a yüklendiğini bildirmek için eylem init ile iframe'e bir mesaj göndereceğiz . İframe kontrol kullanılan handleMessage fonksiyonunda aksiyon widget.ts ve yapılandırma veri içeren bir mesaj gönderir. React uygulaması bu mesajı dinleyecek ve yapılandırma mevcutsa render yöntemini çağıracaktır. Bu, widget'ın her zaman yalnızca yapılandırma mevcut olduktan sonra yüklenmesini sağlar.

React uygulamamız yüklendiğine göre, App.tsx'te koşullu rotamızı oluşturacağız.

App.tsx

 
import React, { useContext, useState } from 'react';
import { IConfig } from './config/interfaces';
import { Context } from './context/context';
import Active from './components/Active';
import Completed from './components/Completed';
import NewTask from './components/NewTask';

const App: React.FC = (props) => {
 const config: IConfig = JSON.parse(useContext(Context));
 const [page, setPage] = useState<Number>(1);
  const renderHeader = () => {
   return (<h3 className="bg-dark p-3 m-0 text-white">Todo-List</h3>);
 }

 const renderLinks = () => {
   return (<div className="nav row m-0 bg-light">
     <a className="nav-link col-4 text-center" href="#" onClick={() => setPage(1)}>Active</a>
     <a className="nav-link col-4 text-center" href="#" onClick={() => setPage(2)}>New</a>
     <a className="nav-link col-4 text-center" href="#" onClick={() => setPage(3)}>Completed</a>
   </div>)
 }

 const renderComponent = () => {
   switch(page) {
     case 1: return <Active config={config}/>
     case 2: return <NewTask setPage={setPage}/>
     case 3: return <Completed config={config}/>
     default: return <Active config={config}/>
   }
 }

 return (<div className="h-100 w-100 border rounded">
   {renderHeader()}
   {renderLinks()}
   {renderComponent()}
 </div>);
}

export default App;

Burada basit bir Yapılacaklar Listesi Uygulaması yapmış olduk. Kodun tamamı için lütfen buraya bakın . Mevcut sayfa bir durum değişkenidir ve bağlantı her tıklandığında değiştirilir. Ve ilgili sayfaların bileşenleri bir switch deyimine göre yüklenir. Tüm sayfaları kurduktan sonra, şimdi html sayfamızda widget yöntemini çağıracağız.

Test için dist klasöründe aşağıdaki kodla index.html adlı bir dosya oluşturalım.

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Webpack with React TS</title>
</head>
<body>
 <script src="http://localhost:9000/Widget.js"></script>
 <script>
   const config = { email: '[email protected]' };
   Widget.init(config);
 </script>
</body>
</html>

Son olarak sunucumuzu başlatıyoruz. 

npm run start

ve http://localhost:5000'i açın . Şimdi, oluşturduğunuz tüm React uygulaması bir iframe'de oluşturulmuş ve yukarıdaki komut dosyasıyla herhangi bir siteye takılabilir.