Box UI Elementsを自前でホスティングする方法

# Box UI Elementsを自前でホスティングする方法

BOX UI Elements は、React.jsを使っていない場合、CDNからJSファイルとCSSファイルをダウンロードして利用します。
これらのリソースをCDNを使わずに自前でホスティングする方法をメモしておきます。

## まとめ (TL;DR)

##### 1. [box-ui-elements](https://github.com/box/box-ui-elements) をCloneして、CSSがフォントを直接参照してる部分を書き換える。

変更するファイルはこれ。
https://github.com/box/box-ui-elements/blob/v12.0.0/src/elements/common/fonts.scss
ビルドする前に、`https://cdn01.boxcdn.net`となってる4箇所を、http://localhost:3000 などに書き換えておきます。
マシなやり方あればおしえてください。

##### 2. box-ui-elemensをビルドする。


```sh:全ロケール、React.js有り無しパターン生成
yarn release:cdn
```

または、

```sh:ja-JPとReact入りのみ
yarn setup; LANGUAGE=ja-JP REACT=true yarn build:prod:dist
```

##### 3. [box-content-preview](https://github.com/box/box-content-preview) をビルドする

```sh
yarn install && yarn build:i18n && yarn build:prod
```

##### 4. Font、box-ui-elements、box-content-preview をホストする

1でダウンロードしておいたFontと、3、4で生成したものをホストします

##### 5. box-ui-elementsの利用時に、optionsに、`staticHost`と`previewLibraryVersion`を指定する

* `staticHost`は自前のホストサーバー
* `previewLibraryVersion`はBUIEが指している単体のPreviewとバージョンが違うときだけ指定

```node 
  contentExplorer.show(folderId, token, {
      container: ".container",           
      staticHost: "http://localhost:3000",
      previewLibraryVersion: "2.58.0"    
  });
```


## Box UI Elementsの構成

前提知識として、Box UI Elementsの注意点を書いておきます。
BOX UI Elements（以降BUIEと表記）に関する詳細は、以下のガイドをまずは参照してください。

BOX DevのUI Elementsガイド
https://ja.developer.box.com/guides/embed/ui-elements/



#### BUIEはPreviewの中身が別プロジェクト

BUIEは、ガイドにあるように、6個のエレメントでできています。
このうち、BUIEのContent Previewは、Preview機能を単体で提供する、box-content-previewのラッパーです。

Github: box-ui-elements
https://github.com/box/box-ui-elements

Github: box-content-preview
https://github.com/box/box-content-preview

BUIEガイドのインストールの箇所で、CDN版のURLが、Content PreviewだけURLのフォーマットとバージョンが違うのは、
実はbox-ui-elementsのContent Previewではなく、box-content-previewのPreviewを向いています。
なぜガイドがこのようになっているのかは不明ですが、BUIEのContent Previewで問題なく動作します。


#### BUIEのPreviewじゃないとサイドバーがでない

このPreviewの2つの大きな違いは、BUIEの方だとサイドバーが出せるということです。
box-content-previewのPreviewだとサイドバーを出せません。

ガイドには書かれていませんが、CDNからインストールするときのURLも、BUIEのPreviewを使いたいときは、その他のElementと同じ形で以下のように指定して取得可能です。
https://cdn01.boxcdn.net/platform/elements/11.0.2/en-US/preview.js

ガイドに書いてある以下のURLは、単体のPreviewのインストールURLとなります。
https://cdn01.boxcdn.net/platform/preview/2.34.0/en-US/preview.js

また、グローバル変数の下に作られるオブジェクト名も以下のように変わります。

BUIE Preview： Box.ContentPreview
単体 Preview: Box.Preview


両者の使い方はほとんど同じですが、受け取るパラメータに少し違いがあります。
具体的な使用方法は、ガイドのBUIE Sidebarのページの、Sidebar with Content Preview のCodePenのコードを読んでもらうとわかりやすいです。
https://developer.box.com/guides/embed/ui-elements/sidebar/#sidebar-with-content-preview

このサンプルを乗せている以上、BUIE ContentPreviewは使って問題無いはずなのに、手動インストールのURLには乗せていないのでガイドが少しバグってます。


#### 単体のPreviewがBUIEのPreviewからどのように参照されているか

BoxのCDN（https://cdn01.boxcdn.net/) には、BUIEと単体Previewとその他いくつかのものがホストされています。
おそらく以下のような構成です。

```
https://cdn01.boxcdn.net/
├── fonts
│   └── (1.0.2 のようなバージョン番号）
│       └── lato
└── platform
    ├── elements
    │   └── (11.0.2 のようなバージョン番号)
    │       ├── (ja-JP のようなロケール名）
    │       └── ...
    └── preview
        ├── （2.58.0 のようなバージョン番号）
        │   ├── （ja-JP のようなロケール名）
        │   └── ...
        └── third-party
            ├── doc
            ├── media
            ├── model3d
            ├── swf
            └── text
```

CDN経由でインストールされたBUIEは、/platform/elements/11.0.2/ja-JP/xxx を読み込みます。
BUIE Content Previewは、コードの中から強制的にscriptタグを作成し、/platform/preview/2.58.0/ja-JP/preview.js の読み込みを行います。
これは、React.jsを利用している際にNPM経由でインストールしても、この方法でpreview.jsを読み込んでいます。
なぜこのようにしているかは不明ですが、おそらく、preview以下に依存するファイルが大量にあり、相対パスで一括で取り込んでしまいたいという意図があるような気がします。

これをおこなっているのが、以下の箇所になります。

BUIE Preivewが単体のPreviewを呼び出すためにスクリプトタグを埋め込む箇所
https://github.com/box/box-ui-elements/blob/1803655c777493c34ccc32ae339445f88cdb1e20/src/elements/content-preview/ContentPreview.js#L466

単体のPreviewが、グローバル変数にPreviewオブジェクトを露出させる箇所
https://github.com/box/box-content-preview/blob/83d687ec9ea7f9ff17380c0590e72a947df9ac73/src/lib/Preview.js#L1944

BUIE Preivewが、グローバルの単体Previewを読み込む箇所
https://github.com/box/box-ui-elements/blob/1803655c777493c34ccc32ae339445f88cdb1e20/src/elements/content-preview/ContentPreview.js#L787


#### Fontの指定が絶対URLになっている

自前でホスティングしようとした場合、BUIEと単体 Preview以外にも、CDNを直接参照しているものがあります。
それが、BUIEのCSSのFontの指定です。


#### 自前でホスティングするためにやらないといけないこと

ここまでをまとめると、BUIEをセルフホスティングしようとしたら、以下のことを行わなければならないということです。

* フォントを絶対URLで指定している箇所を変更する
* BUIEをビルドして、生成されたファイルをホストする
* 単体のPreviewをビルドして、生成されたファイルをホストする
* BUIEのPreviewがコードから単体のPreviewを読み込むので、これを自前のホストに振り向ける


## セルフ・ホスティングを行う

さて、本題です。
ここからホスティングを行う準備をしていきます。

### フォントを絶対URLで指定している箇所を変更する

https://github.com/box/box-ui-elements からCloneします。

以下のファイルを開き、ホストの部分を書き換えます。
https://github.com/box/box-ui-elements/blob/v12.0.0/src/elements/common/fonts.scss

```css:fonts.scss
@font-face {
    font-weight: normal;
    font-family: 'Lato';
    font-style: normal;
    src: local('Lato Regular'), local('Lato-Regular'), url('https://cdn01.boxcdn.net/fonts/1.0.2/lato/Lato-Regular.woff2') format('woff2'), url('https://cdn01.boxcdn.net/fonts/1.0.2/lato/Lato-Regular.woff') format('woff');
}

@font-face {
    font-weight: bold;
    font-family: 'Lato';
    font-style: normal;
    src: local('Lato Bold'), local('Lato-Bold'), url('https://cdn01.boxcdn.net/fonts/1.0.2/lato/Lato-Bold.woff2') format('woff2'), url('https://cdn01.boxcdn.net/fonts/1.0.2/lato/Lato-Bold.woff') format('woff');
}
```

ここでは、`https://cdn01.boxcdn.net`となっている4箇所を、`http://localhost:3000` などに書き換えておきます。
運用環境に合わせて変更してください。
イケてないですね・・ ほかにいいやり方あれば教えてください。
利用するときにCSSで上書きできる気もするのですが・・ 
( @font-faceで名前を変えて定義し、font-familyを!importatで上書きしてみましたがうまくいきませんでした・・・ ）
CDNのフォントはホストできるようにダウンロードしておいてください。


### BUIEをビルドして、生成されたファイルをホストする

BUIEのCDN版のビルド方法は、ガイドに説明はのっていませんが以下のコマンドで行えます。

```sh
yarn release:cdn
```

このコマンドは、package.jsonを見てもらうとわかりますが、/scrips/prod.jsを実行します。
準備されているロケールに対して、Reactが入ってるものと入っていないものをそれぞれ生成します。
この実行は非常に時間がかかり、私の環境では2時間近くかかりました。
もしも日本語用でReactを含んだものだけを生成したい場合、以下のコマンドでクイックに生成可能です。

```sh
yarn setup; LANGUAGE=ja-JP REACT=true yarn build:prod:dist
```

このコマンドを実行すると、/distフォルダ以下に以下がつくられます。
0.0.0-semantically-released というのは、package.jsonのversionの値です。
おそらくCIで、12.0.0のように適切なバージョンに置き換えて生成してるものと思います。

```
.
└── 0.0.0-semantically-released
    └── ja-JP
        ├── explorer.css
        ├── explorer.js
        ├── openwith.css
        ├── openwith.js
        ├── picker.css
        ├── picker.js
        ├── preview.css
        ├── preview.js
        ├── sharing.css
        ├── sharing.js
        ├── sidebar.css
        ├── sidebar.js
        ├── uploader.css
        └── uploader.js

```


### 単体のPreviewをビルドして、生成されたファイルをホストする

次に、単体のPreviewをビルドします。
Content PreviewのSelf Hostingパッケージの作り方は[こちら](https://developer.box.com/guides/embed/ui-elements/installation/#self-hosting-content-preview)に説明があります。（ちょっと間違ってます・・・汗）

正しくは以下です。

```sh
yarn install && yarn build:i18n && yarn build:prod
```

このコマンドを実行すると、/distフォルダに以下が作られます
バージョン番号 2.58.0の下にロケール別のフォルダや、third-partyというフォルダが作られます。




```
.
├── 2.58.0
│   ├── bn-IN
│   ├── (... 省略：サポートされているロケール毎のフォルダ)
│   └── zh-TW
└── third-party
    ├── doc
    │   ├── 1.17.0
    │   ├── ( ... 省略 ）

    ├── media
    │   ├── 0.127.0
    │   ├── (... 省略)
    ├── model3d
    │   ├── 1.10.1
    │   │   └── WebVR
    │   │       ├── (... 省略)
    │   ├── (...省略)
    ├── swf
    │   └── 0.112.0
    └── text
        ├── 0.112.0
        └── 0.114.0

```


あとで解説しますが、ここで生成した2.58.0のようなバージョンは、フォルダ名に反映されますが、このフォルダ名はBUIEのPreviewが指定してきます。
BUIE Previewと単体Previewを連動させるため、バージョンをあわせる必要があります。
BUIE Previewが指定しているバージョンの単体Previewを元に生成するか、指定の単体Previewのバージョンをパラメータで変更します。



## サンプルコードを作って動作確認

Express.jsで動作する簡単なサンプルをつくってみます。


雛形を作ります。

```
mkdir sample
cd sample
npm init -y
npm i express
touch app.js
mkdir lib
```

以下がフォルダ構成です。
`lib` フォルダ（名前は何でも・・）以下に、生成されたファイルを置いてください。


```
.
├── app.js
├── lib
│   ├── fonts
│   │   └── 1.0.2
│   │       └── lato
│   │           ├── Lato-Bold.woff
│   │           ├── Lato-Bold.woff2
│   │           ├── Lato-Regular.woff
│   │           └── Lato-Regular.woff2
│   └── platform
│       ├── elements
│       │   └── 12.0.0
│       │       └── ja-JP
│       └── preview
│           ├── 2.58.0
│           │   ├── bn-IN
│           │   ├── ...
│           └── third-party
│               ├── doc
│               ├── media
│               ├── model3d
│               ├── swf
│               └── text
└── package.json

```

`app.js` はこんな感じです。

```js:app.js

const path = require("path");
const express = require("express");
const app = express();

const HOST = "http://localhost:3000";
const ACCESS_TOKEN = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; //開発者トークンを利用

const oneHour = 1000 * 60 * 60;
app.use(
  "/platform",
  express.static(path.join(__dirname, "lib", "platform"), { maxAge: oneHour })
);
app.use(
  "/fonts",
  express.static(path.join(__dirname, "lib", "fonts"), { maxAge: oneHour })
);

app.get("/explorer", async (req, res) => {
  res.set("Content-Type", "text/html");
  res.send(
    `
    <!DOCTYPE html>
    <html lang="en-US">
    <head>
        <meta charset="utf-8"/>
        <title>BUIE Self Hosting Sample</title>
        <script src="${HOST}/platform/elements/12.0.0/ja-JP/explorer.js"></script>
        <link rel="stylesheet" href="${HOST}/platform/elements/12.0.0/ja-JP/explorer.css"/>
    </head>
    <body>
    <div class="container" style="height:600px"></div>
    
    <script>
        const folderId = "0";
        const token = "${ACCESS_TOKEN}";
        const contentExplorer = new Box.ContentExplorer();
        contentExplorer.show(folderId, token, {
            container: ".container",           
            staticHost: "${HOST}",
            previewLibraryVersion: "2.58.0"    
        });
    </script>
    </body>
    </html>
    `
  );
});

app.get("/preview", async (req, res) => {
  res.set("Content-Type", "text/html");
  res.send(
    `
    <!DOCTYPE html>
    <html lang="en-US">
    <head>
        <meta charset="utf-8"/>
        <title>BUIE Self Hosting Sample</title>
        <script src="${HOST}/platform/elements/12.0.0/ja-JP/preview.js"></script>
        <link rel="stylesheet" href="${HOST}/platform/elements/12.0.0/ja-JP/preview.css"/>
    </head>
    <body>
    <div class="container" style="height:600px"></div>
    
    <script>
        const fileId = "727216965561";
        const token = "${ACCESS_TOKEN}";
        const ContentPreview = new Box.ContentPreview();
        ContentPreview.show(fileId, token, {
            container: ".container", 
            staticHost: "${HOST}",
            previewLibraryVersion: "2.58.0"          
        });
    </script>
    </body>
    </html>
    `
  );
});

const PORT = process.env.PORT || "3000";
app.listen(PORT, () => {
  console.log("Express Server started on port: " + PORT);
});

```


### BUIEのPreview、Explorerがコードから単体のPreviewを読み込むので、これを自前のホストに振り向ける


BUIEのShowメソッドにわたすオプションで、
`staticHost`、`previewLibraryVersion` を指定しています。
これにより、BUIE Content Previewや、BUIE Content Explorerのように、コードから単体のPreviewを読み込む箇所での、CDNのURLを変えることが可能です。


### 動作確認


このコードは`node app.js`で実行すると、
以下のURLでBUIEの動作確認が可能です。

http://localhost:3000/explorer, 
http://localhost:3000/preivew  

ブラウザにChromeをお使いであれば、検証 → Networkから、ダウンロードされるリソースがすべてCDNでなくローカルから落ちてきていることが確認可能です。