はじめに
本エントリーは Vue Advent Calendar 2022 3日目の記事です。
今回は VueJS と地図アプリ開発用 API の ArcGIS API for JavaScript を使用して簡単な地図アプリと印刷機能を作成しました。Vue と ArcGIS API for JavaScript については Esri の ArcGIS API for JavaScript の開発者でもある Rene Rubalcava 氏のブログで以下の記事を参考にしました。
ArcGIS API for JavaScript は ES モジュールとして提供されていますので、Vue や React などの Web フレームワークと一緒に使用することができます。また、今回は Web ページ デザイン用のコンポーネント群の Calcite Design System や sass も利用してみました。
作成したサンプルアプリは、GitHub でも公開していますので、興味のある方は触ってみてください。
開発環境の構築
はじめに VueJS を利用するために vue-cli を使用してインストールを行います。
npm install -g @vue/cli # OR yarn global add @vue/cli
インストールが完了したら次にプロジェクトを作成します。
vue create my-project # OR vue ui
Vue のプロジェクトを作成する場合に Vue のバージョンを聞かれるので、今回は Vue 3 を選択しました。
Vue CLI v5.0.8 ? Please pick a preset: (Use arrow keys) > Default ([Vue 3] babel, eslint) Default ([Vue 2] babel, eslint) Manually select features
問題がなければ Vue のプロジェクトが作成されます。 作成したプロジェクトに移動して npm run serve で実行するとデフォルトの Vue のプロジェクトが表示されます。これで Vue の環境構築は完了です。
$ cd my-project $ npm run serve
最後に ArcGIS API for JavaScript や Calcite Design System などをインストールします。
npm i @arcgis/core @esri/calcite-components copy-webpack-plugin sass sass-loader resolve-url-loader
これですべての開発環境の準備ができました。 いよいよ ArcGIS 側の実装に入っていきます。
ArcGIS 側の実装
地図部分の実装は、ArcGIS API for JavaScript を使用します。src/data/map.js を作成し、独自のモジュールとして処理するように作成します。ArcGIS API for JavaScript の実装方法については、以下のチュートリアルを参照して下さい。
また、カスタムの sass を使用するため、ArcGIS API for JavaScript 用のアセットをローカルにコピーする必要があります。通常は CDN からスタイルやアセットを読み込めがいいのですが、今回はローカスから参照するようにしました。
ArcGIS API for JavaScript による src/data/map.js のコードは、以下になります。
// src/data/map.js import config from '@arcgis/core/config'; import MapView from '@arcgis/core/views/MapView'; import WebMap from '@arcgis/core/WebMap'; import Expand from '@arcgis/core/widgets/Expand'; import Legend from '@arcgis/core/widgets/Legend'; import Print from '@arcgis/core/widgets/Print'; config.assetsPath = './assets'; export const webmap = new WebMap({ portalItem: { id: 'f2e9b762544945f390ca4ac3671cfa72' } }); export const view = new MapView({ container: 'viewDiv', map: webmap }); export const legend = new Expand({ content: new Legend({ view, style: 'card' }), view, expanded: true }); view.ui.add(legend, 'bottom-left'); /** * Assigns the container element to the View * @param container */ export const initialize = (container) => { view.container = container; view.when() .then(() => { console.log('Map and View are ready'); }) .catch(error => { console.warn('An error in creating the map occurred:', error); }); }; export function initializePrint(container) { const print = new Print({ view, container }); return print; }
次に WebMap.vue ですが、map で実装した地図を表示するために使用される要素を取得する initialize() メソッドを使用します。そして、このモジュールは VueJS コンポーネント内で使用することができます。
// src/components/WebMap.vue <template> <div class="map-div"></div> </template> <script> export default { name: 'WebMap', async mounted() { const app = await import('../data/map'); app.initialize(this.$el); } }; </script> <style lang="scss"> </style>
印刷機能の表示は Print.vue にて実装しています。
<template> <div class="print-div"></div> </template> <script> export default { name: 'Print', async mounted() { const app = await import('../data/map'); app.initializePrint(this.$el); } }; </script> <style lang="scss"> .print-div { position: absolute; right: 0; z-index: 999; width: 300px; height: 450px; } </style>
Sass
ここで sass を触っていきます。独自のカスタム sass を構築することの素晴らしい点は、アプリケーションに必要な sass の部分を決定できることです。ArcGIS API for JavaScript の各ウィジェットには、独自の sass ファイルが付属しています。また、これらのファイルをビルドに含めるかどうかを決定するために、一連のフラグを設定することができます。つまり、ウィジェットを使用しない場合、ビルドの CSS からそのスタイルを除外することができます。
// src/components/WebMap.vue <style lang="scss"> // Widgets (sorted alphabetically) $include_AreaMeasurement2D: false !default; $include_AreaMeasurement3D: false !default; $include_Attachments: false !default; $include_BasemapGallery: false !default; $include_BasemapLayerList: false !default; $include_BasemapToggle: false !default; $include_BinaryColorSizeSlider: false !default; $include_Bookmarks: false !default; $include_BuildingExplorer: false !default; $include_ButtonMenu: false !default; $include_ClassedColorSlider: false !default; $include_ClassedSizeSlider: false !default; $include_Compass: false !default; $include_ColorPicker: false !default; $include_ColorSizeSlider: false !default; $include_ColorSlider: false !default; $include_CoordinateConversion: false !default; $include_DatePicker: false !default; $include_Daylight: false !default; $include_Directions: false !default; $include_DirectLineMeasurement3D: false !default; $include_DistanceMeasurement2D: false !default; $include_Editor: false !default; $include_ElevationProfile: false !default; $include_Feature: false !default; $include_FeatureContent: false !default; $include_FeatureMedia: false !default; $include_FeatureForm: false !default; $include_FeatureTable: false !default; $include_FeatureTemplates: false !default; $include_FloorFilter: false !default; $include_Grid: false !default; $include_HeatmapSlider: false !default; $include_Histogram: false !default; $include_HistogramRangeSlider: false !default; $include_IdentityForm: false !default; $include_IdentityModal: false !default; $include_ItemList: false !default; $include_LayerList: false !default; $include_LineOfSight: false !default; $include_Measurement: false !default; $include_NavigationToggle: false !default; $include_OpacitySlider: false !default; $include_Print: false !default; $include_ScaleBar: false !default; $include_ScaleRangeSlider: false !default; $include_Search: false !default; $include_ShadowAccumulation: false !default; $include_SizeSlider: false !default; $include_Sketch: false !default; $include_Slice: false !default; $include_Slider: false !default; $include_SnappingControls: false !default; $include_Spinner: false !default; $include_Swipe: false !default; $include_TableList: false !default; $include_TimePicker: false !default; $include_TimeSlider: false !default; $icomoon-font-path: "./assets/esri/themes/base/icons/fonts" !default; $calcite-fonts-path: "./assets/esri/themes/base/fonts/fonts/" !default; @import "~@arcgis/core/assets/esri/themes/light/main.scss"; .map-div { padding: 0; margin: 0; width: 100%; height: 100%; } </style>
API には沢山のウィジェットと呼ばれる機能群があります。その他、いくつかの重要な変数がここにリストアップされています。これらは、フォントがコピーされたときにフォントを探す場所を指定します。
$icomoon-font-path: "./assets/esri/themes/base/icons/fonts" !default; $calcite-fonts-path: "./assets/esri/themes/base/fonts/fonts/" !default;
これで、このコンポーネントを別のコンポーネントで使用することができます。このコンポーネントは Calcite レイアウトコンポーネントを使用し、マップを囲みます。たくさんのコンポーネントがあります。
<template> <calcite-shell> <slot name="shell-header"> <header class="header"> <calcite-icon icon="beaker" scale="m" aria-hidden="true"></calcite-icon> <h2 class="heading">ArcGIS Vue and Calcite</h2> </header> </slot> <WebMap /> </calcite-shell> </template> <script> import { applyPolyfills, defineCustomElements } from "@esri/calcite-components/dist/loader"; import WebMap from './components/WebMap.vue' applyPolyfills().then(() => { defineCustomElements(window); }); export default { name: 'App', components: { WebMap } } </script> <style lang="scss"> @import "~@esri/calcite-colors/dist/colors"; // calcite colors </style>
注意点としては、VueJS は slot 属性を好まないのですが、slot を要素のように扱うことができます。calcite-components は Stencil で作られた Web コンポーネントなので、いくつかの polyfills と、それを読み込むための loader が存在します。ここではすべての sass スタイルを追加していませんが、calcite の sass 変数を使用したい場合は、色の変数をインポートします。
ArcGIS API for JavaScript と calcite で VueJS を使用するには、これでほぼ完了です。すべての sass を正しくコピーしてビルドするために、もう少し設定が必要です。
Build (ビルド) の設定
VueJS は、内部で webpack を使用しています。他の cli ツールとは異なり、webpack のビルドにフックし、config によって拡張する方法を提供しています。使用したい webpack プラグインは configureWebpack で追加することができます。
// vue.config.js const CopyPlugin = require('copy-webpack-plugin'); const jsapi = '@arcgis/core'; module.exports = { configureWebpack: { plugins: [ new CopyPlugin({ patterns: [ // calcite assets { context: 'node_modules', from: '@esri/calcite-components/dist/calcite', to: './', }, // arcgis assets { context: 'node_modules', from: `${jsapi}/assets`, to: './assets', globOptions: { // ignore the webscene spec folder, sass files, ignore: ['**/webscene/spec/**', '**/*.scss', '**/*.css'], }, }, ] }), ] }, chainWebpack: (config) => { ['vue-modules', 'vue', 'normal-modules', 'normal'].forEach((rule) => { config.module .rule('scss') .oneOf(rule) .use("resolve-url-loader") .loader("resolve-url-loader") .options({ sourceMap: true, // eslint-disable-next-line no-unused-vars join: (_rul_uri, _rul_base) => { // args must be included return (arg) => { return arg.bases.value.includes("@arcgis/core") ? arg.uri.replace("../", "./assets/esri/themes/") : arg.uri; }; }, }) .before("sass-loader") .end() .use("sass-loader") .loader("sass-loader") .tap((options) => ({ ...options, sourceMap: true })) .end() .use('css-loader') .loader('css-loader') .tap((options) => ({ ...options, url: false, importLoaders: 2 })) .end() }); }, css: { extract: { filename: '[name].css', chunkFilename: '[name].css', }, }, };
ArcGIS 側の実装はこれで完了ですので、以下のコマンドで地図の動きが確認できるかと思います。
$ npm run serve
最後は Web アプリをデプロイするためにビルドを実行します。
$ npm run build
そうすると dist フォルダ内にビルドされたコンテンツが生成されますので、 その dist を任意の静的ファイル サーバーにデプロイすることができます。詳細については、以下の Vue のガイドが参考になります。
ビルドの前に vue.config.js で publicPath を設定する必要もあります。今回 GitHub のリポジトリが vuejs で作成しているため、以下のように vuejs を設定しています。
publicPath: process.env.NODE_ENV === 'production' ? '/vuejs/' : '/',
簡単ですが、GitHub Pages に公開するため GitHub へは以下のコマンドでデプロイしました。
cd dist git init git add -A git commit -m 'deploy' git branch -M main git remote add origin https://github.com/valuecreation/vuejs.git git push -u origin main
実はデプロイ後に GitHub Pages で確認すると CSS のパスが間違っており、アイコンなどの参照で 404 エラーが発生していました。そのため、以下のように app.css ファイルで assets で始める場所のパスをすべて修正しました。
url(../../assets/esri/themes/base/fonts/fonts/e388ac99-8c6a-4451-8690-1d15b4d45adb.woff) ↓ 修正 url(./assets/esri/themes/base/fonts/fonts/e388ac99-8c6a-4451-8690-1d15b4d45adb.woff)
今回作成したプロジェクトは以下にまとめてあります。 github.com
まとめ
Vue は初めて触ってみました。見よう見まねで触ってみて、ビルドとデプロイまではできました。実際に Vue を使って Web アプリを開発する場合は、Vue に関してはまだ理解できていないので、Vue そのものの理解は必要だなと感じました。Vue に関しては日本語でもガイドは多く提供されていますので、時間を見つけて勉強していきたいと思います。
ArcGIS API for JavaScript は ES モジュールとして提供されていますので、Vue に限らず他のフレームワークでも利用することができます。以下のガイドや GitHub にも公開されていますので、ぜひ、触ってみていただけるとうれしいです。