2024年9月9日月曜日

ReactのクイックスタートをOracle APEXで動かしてみる

Reactの勉強を兼ねて、スタートガイドのクイックスタートをOracle APEXのアプリケーションに実装してみます。Oracle APEXではReactのJSXをそのまま扱うことはできないため、ブラウザ上でのJSXの実行とJSXファイルのコンパイルにBabelを使用します。

最初に空のAPEXアプリケーションを作成します。名前React Quick Startとしました。

アプリケーションの作成を実行します。


アプリケーションが作成されます。

このアプリケーションのホーム・ページにReactのコンポーネントを埋め込みます。


作成予定のReactのコンポーネントの多くは、ローカルのPC上にファイルとして作成します。作成したJSXファイルをBabelを使ってJavaScriptのファイルにコンパイルし、APEXのアプリケーションに組み込みます。

JSXファイルを配置するフォルダを作成します。名前はquickstartとしました。

フォルダquickstartreactおよびreact-domをインストールします。インストールするパッケージのバージョンを明示していますが、これはブラウザに設定するimportmapを、npmでインストールしたreact、react-domのバージョンと一致させるためです。

npm install react@18.3.1 react-dom@18.3.1

 quickstart % npm install react@18.3.1 react-dom@18.3.1


added 5 packages in 250ms

 quickstart % 


Babelをインストールします。

npm install --save-dev @babel/core @babel/cli @babel/preset-react

 quickstart % npm install --save-dev @babel/core @babel/cli @babel/preset-react


npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.

npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported


added 89 packages, and audited 95 packages in 804ms


8 packages are looking for funding

  run `npm fund` for details


found 0 vulnerabilities

 quickstart % 


JSXのコンパイルを行うため、.babelrcにプリセットとして@babel/preset-reactを設定します。ファイル.babelrcを作成し、以下の内容を記述します。
{
  "presets": ["@babel/preset-react"]
}

 quickstart % cat .babelrc 

{

  "presets": ["@babel/preset-react"]

}

 quickstart % 


VS Codeを起動し、フォルダquickstartを開きます。

Oracle APEXのセッション・オーバーライドを活用して、ローカルのPC上のファイルを静的アプリケーション・ファイルとして参照できるようにします。


フォルダquickstart直下にファイルindex.htmlを作成します。ファイルには以下を記述します。
<html>
    <body>
        Live Server is ready.
    </body>
</html>
作成したファイルindex.htmlを保存します。コンテキスト・メニューを開き、Open with Live Serverを実行します。


ブラウザの画面が開き、Live Server is ready.と表示されます。アクセスしているURLは以下になります。

http://127.0.0.1:5500/index.html


これでセッション・オーバライドを使って、フォルダquickstart以下のファイルを静的アプリケーション・ファイルとして参照できるようになりました。

すでに静的アプリケーション・ファイルとして作成済みのアイコン・ファイルを、フォルダquickstart以下にコピーします。

共有コンポーネント静的アプリケーション・ファイルを開きます。

Zipとしてダウンロードをクリックし、静的アプリケーション・ファイルをZip形式で手元のPCにダウンロードします。ダウンロードされるZipファイルの名前は以下の形式になります。

f<アプリケーションID>_static_application_files.zip


ダウンロードされたZipファイルをフォルダquickstart以下に展開します。

unzip ~/Downloads/f165_static_application_files.zip

 quickstart % unzip ~/Downloads/f165_static_application_files.zip


Archive:  /___/_______/Downloads/f165_static_application_files.zip

Implementation by Anton Scheffer

 extracting: icons/app-icon-144-rounded.png  

  inflating: icons/app-icon-192.png  

  inflating: icons/app-icon-256-rounded.png  

 extracting: icons/app-icon-32.png   

  inflating: icons/app-icon-512.png  

 quickstart % 


作成したAPEXアプリケーションを実行し、セッション・オーバーライドを設定します。

開発者ツール・バーセッション・オーバーライドを開きます。


ファイル・パスアプリケーション・ファイルオンにします。セッション・オーバーライドの有効化も同時にオンに変わります。アプリケーション・ファイルにLive Serverの待ち受けURLである、以下の値を設定します。

http://127.0.0.1:5500/

変更を保存します。


セッション・オーバーライドが有効になっているか確認するため、表示されているアイコンの画像アドレスのコピーを実行します。


コピーした画像アドレスが以下であれば、セッション・オーバーライドは有効です。

http://127.0.0.1:5500/icons/app-icon-512.png

これから、ホーム・ページにReactコンポーネントを実装していきます。

ホーム・ページのページ・プロパティHTMLヘッダーに、Reactを読み込むための
importmapを記述します。
<script type="importmap">
    {
        "imports": {
            "react": "https://cdn.jsdelivr.net/npm/react@18.3.1/+esm",
            "react-dom": "https://cdn.jsdelivr.net/npm/react-dom@18.3.1/+esm"
        }
    }
</script>
reactとreact-domのバージョンは必ず一致させます。バージョンが未指定であったり、最新バージョンを選択するようにしていると、reactとreact-domのバージョンが一致しないことがあります。その場合、useStateなどのフックの利用時にエラーが発生することがあります。(例えばcannot read properties of null (reading 'useState')といったエラー)。

できるだけエラーを発生させないように、npmでinstallしたreactおよびreact-domのバージョンと、importmapのバージョンは一致させます。

また、Babelを呼び出すためにJavaScriptファイルURLに以下を記述します。

https://cdn.jsdelivr.net/npm/@babel/standalone/babel.min.js


最初にJSXファイルを作成せず、APEXのリージョンに直接Reactコンポーネントを記述してみます。

静的コンテンツのリージョンとしてMyButtonを作成します。ソースHTMLコードとして以下を記述します。SCRIPT要素のtypeとしてtext/babelを設定し、Babelを呼び出すことでJSXを解釈します。



アプリケーションを実行すると、以下のように表示されます。


同じコンポーネントを、今度はApp.jsxという名前のファイルとして作成します。作成したApp.jsxをBabelでJavaScriptファイルにコンパイルし、コンパイルされたApp.jsをAPEXアプリケーションに読み込みます。



App.jsxからJavaScriptファイルApp.jsを作成します。

npx babel App.jsx --out-file App.js

 quickstart % npx babel App.jsx --out-file App.js

 quickstart % 


作成済みのリージョンMyButtonコメント・アウトし、新たにリージョンAppを作成して、HTMLコードに以下を記述します。
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React from 'react';
import ReactDOM from 'react-dom';
import App from "#APP_FILES#App.js";

/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <App /> );
</script>

ホーム・ページを実行すると、以下のように表示されます。表示自体は、先ほどのMyButtonリージョンと同じです。


次にProfile.jsxを作成します。コードは以下です。


Babelでコンパイルします。

npx babel Profile.jsx --out-file Profile.js

作成済みのリージョンAppコメント・アウトし、新たにリージョンProfileを作成して、HTMLコードに以下を記述します。import文とrender文に指定されていたAppをProfileに置き換えています。
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React from 'react';
import ReactDOM from 'react-dom';
import Profile from "#APP_FILES#Profile.js";

/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <Profile /> );
</script>

ホーム・ページを実行すると、以下のように表示されます。


CSSの指定が不足しているので、ページ・プロパティのCSSのインラインに以下を追記します。APEXでのクイックスタートの実装は、IDがcontainerのdiv要素をReactコンポーネントのrootとしているため、#contaierをスコープとしています。
@scope(#container) {
    .avatar {
        border-radius: 50%;
    }
}


CSSを適用すると、Reactのクイックスタートと同じように表示されます。


ShoppingList.jsxを作成します。コードは以下です。


Babelでコンパイルします。

npx babel ShoppingList.jsx --out-file ShoppingList.js

作成済みのリージョンProfileコメント・アウトし、新たにリージョンShoppingListを作成して、HTMLコードに以下を記述します。
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React from 'react';
import ReactDOM from 'react-dom';
import ShoppingList from "#APP_FILES#ShoppingList.js";

/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <ShoppingList /> );
</script>
ホーム・ページを実行すると、以下のように表示されます。


フックのuseStateを使ったボタンをクリックするサンプルを、MyApp.jsxとして作成します。


Babelでコンパイルします。

npx babel MyApp.jsx --out-file MyApp.js

作成済みのリージョンShoppingListコメント・アウトし、新たにリージョンMyAppを作成して、HTMLコードに以下を記述します。
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React from 'react';
import ReactDOM from 'react-dom';
import MyApp from "#APP_FILES#MyApp.js";

/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <MyApp /> );
</script>
ホーム・ページを実行すると、以下のように表示されます。


ボタンが横並びになっているのはさておき、ボタンをクリックするとAPEXのデフォルトの動作であるページの送信が行われてしまいます。

ページ・プロパティJavaScriptページ・ロード時に実行に以下のJavaScriptを記述し、Reactコンポーネント内でのボタンクリックでページの送信が行われないようにします。


ページ送信を抑制すると、useStateを使ったサンプルが想定通りに動作します。


コンポーネント間でデータを共有する実装を、MyApp2.jsxとして作成します。

Babelでコンパイルします。

npx babel MyApp2.jsx --out-file MyApp2.js

作成済みのリージョンMyAppコメント・アウトし、新たにリージョンMyApp2を作成して、HTMLコードに以下を記述します。
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import MyApp from "#APP_FILES#MyApp2.js";

/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <MyApp /> );
</script>
ホーム・ページを実行すると、以下のように表示されます。


チュートリアルの三目並べを実装します。Game.jsxを作成します。


Babelでコンパイルします。

npx babel Game.jsx --out-file Game.js

作成済みのリージョンMyApp2コメント・アウトし、新たにリージョンGameを作成して、HTMLコードに以下を記述します。
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React from 'react';
import ReactDOM from 'react-dom';
import Game from "#APP_FILES#Game.js";

/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <Game /> );
</script>
ページ・プロパティCSSインラインに以下を追記します。ファイルを作成してインポートしても良いでしょう。



ホーム・ページを実行すると、以下のように表示されます。


チュートリアルのReactの流儀のセクションで紹介されているアプリケーションを実装します。App3.jsxを作成します。


Babelでコンパイルします。

npx babel App3.jsx --out-file App3.js

作成済みのリージョンGameコメント・アウトし、新たにリージョンApp3を作成して、HTMLコードに以下を記述します。
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React from 'react';
import ReactDOM from 'react-dom';
import App from '#APP_FILES#App3.js';

/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <App /> );
</script>
ホーム・ページを実行すると、以下のように表示されます。


Reactのクイックスタートを、一通りOracle APEXで動かせました。

Babelでコンパイルして作成したJavaScriptファイルを静的アプリケーション・ファイルとして保存することにより、ローカルのファイルを参照せずにReactコンポーネントを含むAPEXアプリケーションを実行できます。

共有コンポーネント静的アプリケーション・ファイルを開きます。

ファイルの作成をクリックします。


コンテンツとして作成したJavaScriptファイル(App.jsProfile.jsShoppingList.jsMyApp.jsMyApp2.jsGame.jsApp3.js)をそれぞれ、作成します。


ファイルをすべてアップロードすると、以下のようになります。APEXの静的アプリケーション・ファイルとしてJavaScriptファイルを作成すると、自動的にミニファイされたファイルも作成されますが、ファイルをアップロードした場合はミニファイされません。

今回はミニファイされたファイルは使用していないため、ミニファイは行いません。


JavaScriptファイルをアップロードしたら、セッション・オーバーライドを外します。


セッション・オーバーライドを外すと、以下のエラーが発生します。

Failed to resolve module specifier "r/apexdev/165/files/static/v22/App3.js". Relative references must start with either "/", "./", or "../".


内容としては、import from の指定としてr/apexdev/165/... という形式ではなく、相対パスの場合は、/./../ で始まるように記述してください、と言われています。 

import文を以下のように書き換えるとエラーは発生しなくなりますが、セッション・オーバーライドを使って、Live Serverを指定できなくなります。

import App from './#APP_FILES#App3.js';

今回はアプリケーション定義ユーザー・インターフェース詳細#APP_FILES#のパスを設定することで、このエラーを回避します。

./r/apexdev/165/files/static/v22/

./以降に付与される文字列はAPEXワークスペース名アプリケーションIDが含まれるため、環境やアプリケーションごとに値は異なります。


以上の変更を実施することで、Reactのクイックスタートで作成しているアプリケーションをAPEXのアプリケーションに組み込むことができました。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/react-quick-start.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。

2024年9月6日金曜日

v0 by Vercelを使って個人旅行管理アプリを作成してみた

Oracle APEXに直接関係ありませんが、巷で話題のv0 by Vercelを使ってみました。先日、生成AIを活用してOracle APEXのアプリケーションを作成するという記事を書きました。目的は同じアプリケーションをv0 by Vercelで作成してみます。

Public Betaへの参加方法などは色々な方が書いている記事を参考にしていただくか、公式サイトを参考にしてください。

Getting Startedをクリックして、チャットを始めます。

以下のプロンプトを与えます。
私の個人旅行を管理するアプリケーションを作成します。旅行名、日程、旅行先、宿泊地、移動手段、費用、アクティビティなどが管理できて、ひとつの旅行で複数の宿泊地を移動することもあります。

プロンプトひとつでここまでアプリが出来上がるのには驚きました。


 ボタンなども一応は動作します。

コードを見ると、(特に指示をしないと)データはReactのStateとして保存しているようです。


以下のプロンプトを入力します。

データはMongoDBに保存するようにしてください。

GETとPOSTを受け付けるハンドラーのコードを生成し、POSTでJSONドキュメントを受け付けてMongoDBのコレクションに対してinsertOneを呼び出すコードが生成されています。

Reactアプリケーションの方では、データの保存でPOSTを呼び出すように書き換わっています。


以下のプロンプトを与えます。

MongoDBの代わりにMySQLを使うように変更してください。

schema.sqlとして、CREATE TABLE文を生成しています。作成した表には参照制約まで付けられています。


おそらく、ユーザー・インターフェースの部分についてはチャットによる指示を繰り返すことで、かなり改善できる印象です。

以下の2点が気になります。
  1. AIがコードを書いてくれるけど、やはりデバッグができるだけのスキルは必要だと感じます。
  2. ユーザー・インターフェースに合わせて、データベースが設計されるように見受けられます。ユーザー・インターフェースとは独立してスキーマをモデリングしている場合、スキーマ定義を渡した上で(スキーマ定義とは必ずしも一致しない)ユーザー・インターフェースの作成をプロンプトとして与えても、その違いに合わせたコードを生成してくれるのだろうか。
とはいえ、Oracle Database 23aiであれば、JSONコレクション表JSONリレーショナル・デュアリティの機能があるので、スキーマがどのように定義されているかにかかわらず、JSONでのデータのやり取りができます。AIが生成したユーザー・インターフェースに基づいたデータ定義とバックエンドのデータベースでの定義の違いは、データベース側で吸収できるはずです。

Oracle DatabaseのJSON関連の機能も、より注目されるかもしれません。

2024年9月5日木曜日

Oracle APEXでthree.jsのGetting Startedに取り組んでみる

Oracle CloudのAlways Free枠で使用できるOracle APEXを使って、three.jsのGetting Startedに掲載されているExampleをいくつか実装してみます。使用するデータベースはAutonomous Transaction Processingです。

最初に空のAPEXアプリケーションを作成します。名前three.js Getting Startedとします。


アプリケーションが作成されます。

今回はJavaScriptの記述が多いので、VS Codeを使って開発することにします。ローカルで開発できるように、共有コンポーネントの静的アプリケーション・ファイルを手元のPCにダウンロードします。

共有コンポーネントを開きます。


静的アプリケーション・ファイルを開きます。


Zipファイルとしてダウンロードをクリックし、保存されている静的アプリケーション・ファイルをダウンロードします。


手元のPCに適当なフォルダを作成し、そこにZipファイルの内容を展開します。

今回はthree-examplesというディレクトリを作成し、そこにダウンロードしたfnnn_static_application_files.zipnnnの部分はアプリケーションID)を展開しました。

iconsというフォルダが作成され、その下にPNG形式のファイルが保存されます。

% pwd

/......................../three-examples

three-examples % ls -lR

total 0

drwxr-xr-x@ 7 **********  staff  224  9  5 14:57 icons


./icons:

total 72

-rw-rw-rw-@ 1 **********  staff   2688  9  5 05:53 app-icon-144-rounded.png

-rw-rw-rw-@ 1 **********  staff   3043  9  5 05:53 app-icon-192.png

-rw-rw-rw-@ 1 **********  staff   5461  9  5 05:53 app-icon-256-rounded.png

-rw-rw-rw-@ 1 **********  staff    522  9  5 05:53 app-icon-32.png

-rw-rw-rw-@ 1 **********  staff  12362  9  5 05:53 app-icon-512.png

three-examples % 


VS Codeで作成したフォルダを開きます。

開いたフォルダ直下にファイルindex.htmlを作成します。Live Serverを起動するためだけに使用するファイルなので、以下の内容で十分です。
<html>
<body>
    LiveServer is available.
</body>
</html>

VS Codeの拡張機能としてLive Serverを入れておきます。


作成したindex.html上でコンテキスト・メニューを開きます。

メニューよりOpen with Live Serverを実行します。


ブラウザのウィンドウが開き、index.htmlがLive Server経由で開きます。以下のURLにアクセスしているはずです。

http://127.0.0.1:5500/index.html

同じフォルダに存在する他のファイルも、http://127.0.0.1:5500/からアクセスできるようになりました。


作成した空のAPEXアプリケーションを実行します。

アプリケーションにサインインしたのち、開発者ツール・バーセッションより、セッション・オーバーライドを開きます。


セッション・オーバーライドファイル・パスに含まれるアプリケーション・ファイルオンに切り替え、値にLive Serverの待ち受け先URLであるhttp://127.0.0.1:5500/を設定します。

設定を保存します。


セッション・オーバーライドの設定を保存しても、ページの表示が変わったようには見えません。アイコン上でコンテキスト・メニューを表示し、画像アドレスをコピーを実行します。

コピーした画像アドレスが以下のようにLive Serverを指していれば、VS Codeによる開発を行う準備は完了です。

http://127.0.0.1:5500/icons/app-icon-512.png


最初にGetting StartedのCreating a sceneに取り組みます。

新規に空白のページを作成します。ページの名前Creating a sceneナビゲーションブレッドクラムの使用オフにします。


ページが作成されます。Getting StartedのInstallationOption 2: Import from a CDNを参考にして、このページでthree.jsが使えるようにします。npm version listを開き、threeの最新バージョンを確認します。2024年9月5日時点では、0.168.0が最新でした。

以下のimportmapページ・プロパティHTMLヘッダーに記述します。
<script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@0.168.0/build/three.module.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.168.0/examples/jsm/"
    }
  }
</script>

以上でこのページでthree.jsを呼び出す準備ができました。

Creating a sceneの例は、最終的にはmain.jsというファイルに実装されます。例題のコードではbody直下にthree.jsの描画領域を配置するようになっています。APEXのページ内に表示させるために、描画領域をリージョンに含めるように変更します。

静的コンテンツのリージョンを作成し、ソースHTMLコードとして以下を記述します。

<div id="container"></div>


この状態でページを実行すると、以下のように表示されます。


VS Codeで以下のファイルmain.jsを作成します。



ファイルを作成したら、ページ・プロパティJavaScriptファイルURLに、main.jsを参照する行を記述します。セッション・オーバーライドが有効なので、置換文字列APP_FILESは、Live Serverを指します。

[module, defer]#APP_FILES#main.js

ページの変更を保存します。


先ほどのページを再ロードすると、以下のように表示されます。


次にDrawing linesの例題に取り組みます。

Creating a sceneのページをコピーして、Drawing linesのページを作成します。

ページの作成を実行し、コピーとしてのページの作成を選択します。


次のコピーとしてのページを作成は、このアプリケーションのページです。

へ進みます。


コピー元のページとして、すでにthree.jsを実装した2. Creating  a sceneを選択します。新規ページ番号新規ページ名Drawing linesとします。

へ進みます。


ナビゲーションのプリファレンスとして新規ナビゲーション・メニュー・エントリの作成を選択します。新規ナビゲーション・メニュー・エントリはデフォルトでDrawing linesになります。

へ進みます。


リージョンのタイトルをCreating a sceneからDrawing linesに変更します。

以上でコピーを実行します。


ページのコピーが作成されます。

ページ・プロパティJavaScriptファイルURLを、ファイルdrawingLines.jsを参照するように変更します。

[module, defer]#APP_FILES#drawingLines.js


VS Codeの作業に移ります。ファイルdrawingLines.jsを作成します。内容は以下になります。

ページの保存と実行を行います。


ページ上に2本の青い線で、上を示す矢印が書かれていることが確認できます(以下のスクリーンショットでの確認は難しいですが、書いてあります)。


最後にExamplesのanimation / keyframesを実装してみます。

ページのコピーまでは先ほどと同様に実施します。ページ名リージョン名animation keyframesとします。

keyframesを実装するファイルはkeyframes.jsとします。ページ・プロパティJavaScriptファイルURLの記載は以下になります。

[module, defer]#APP_FILES#keyframes.js


animation / keyframesの右下にあるボタンをクリックし、例題のソースにアクセスします。


GitHubのリポジトリにあるファイルが開きます。


静的コンテンツソースHTMLコードを以下に置き換えます。著作権に関する記述を追加しています。
<div id="container"></div>

<div id="info">
	<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - animation - keyframes<br/>
	    Model: <a href="https://artstation.com/artwork/1AGwX" target="_blank" rel="noopener">Littlest Tokyo</a> by
    <a href="https://artstation.com/glenatron" target="_blank" rel="noopener">Glen Fox</a>, CC Attribution.
</div>


VS Codeに移り、ファイルkeyframes.jsを作成します。以下のコードを記述します。


keyframs.jsが参照しているファイルをGitHubのリポジトリからコピーします。

以下のファイルをmodels/gltf/LittlestTokyo.glbとして保存します。
https://github.com/mrdoob/three.js/blob/master/examples/models/gltf/LittlestTokyo.glb

以下のファイルは、jsm/libs/draco/gltf以下にコピーします。
https://github.com/mrdoob/three.js/blob/master/examples/jsm/libs/draco/gltf/draco_decoder.js
https://github.com/mrdoob/three.js/blob/master/examples/jsm/libs/draco/gltf/draco_decoder.wasm
https://github.com/mrdoob/three.js/blob/master/examples/jsm/libs/draco/gltf/draco_wasm_wrapper.js

VS Codeのエクスプローラーからは、コピーしたファイルは以下のように見えます。


以上でページを実行すると、以下のように表示されます。


three.jsの例題の実装は以上です。

今のままではLive Serverからファイルを参照しているため、作成したアプリケーションを他の人と共有することができません。すべてのファイルを静的アプリケーション・ファイルとして、APEXにアップロードするとアプリケーションの共有はできますが、アプリケーションの開発効率やデータベースへの負荷を考慮すると、あまり良い方法とは思えません。

現行のバージョンのAPEXは静的アプリケーション・ファイルの保存先および参照先を、オブジェクト・ストレージにすることができます。APEX 23.2で追加された機能で、こちらの記事で紹介しています。


また、静的アプリケーション・ファイルの参照先にCDNを指定するといったことも可能です。


APEXのアプリケーションは全体としてエクスポート/インポートすることになり、一般のソースコードのように差分をマージするといった作業はほぼできませんが、コードを静的アプリケーション・ファイル(または静的ワークスペース・ファイル)としてAPEXの外に出した場合は、通常のファイルとして管理できます。

JavaScriptのコーディングが主になる場合は、コードを記述したファイルをAPEXの外で管理すると、開発効率を上げることができると思います(PL/SQLでもコードをパッケージにまとめると、同様に管理できます)。

今回の記事は以上になります。

静的アプリケーション・ファイルは含んでいないため、ほとんど中身がないAPEXアプリケーションですが、エクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/three-js-getting-started.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。