Как включить ArcGIS Javascript API через CDN с Webpack для работы со Stimulus?

В кратком руководстве по api javascript ArcGIS содержится следующий пример кода:

<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
  <title>ArcGIS API for JavaScript Hello World App</title>
  <style>
    html, body, #viewDiv {
      padding: 0;
      margin: 0;
      height: 100%;
      width: 100%;
    }
  </style>

  <link rel="stylesheet" href="https://js.arcgis.com/4.12/esri/css/main.css">
  <script src="https://js.arcgis.com/4.12/"></script>

  <script>
    require([
      "esri/Map",
      "esri/views/MapView"
    ], function(Map, MapView) {

      var map = new Map({
        basemap: "topo-vector"
      });

      var view = new MapView({
        container: "viewDiv",
        map: map,
        center: [-118.71511,34.09042],
        zoom: 11
      });

    });
  </script>
</head>
<body>
  <div id="viewDiv"></div>
</body>
</html>

Что отлично подходит для простой веб-страницы. Однако я использую Blazor (на стороне сервера), и я хотел бы инкапсулировать (выше) код в компонент Blazor. Итак, я наткнулся на первый камень преткновения - мне нельзя добавлять<script>внутри компонента Blazor. Это потому, что элемент управления может быть создан динамически в любое время. Поэтому я подумал, что вместо этого решу проблему с помощью Stimulus.

Вот мой компонент Blazor (пока). У меня есть файл под названиемMap.razor:

<div data-controller="map"></div>

@code {
    protected override bool ShouldRender()
    {
        var allowRefresh = false;

        return allowRefresh;
    }
}

Я добавил ShouldRenderтак что компонент будет отображаться только один раз (когда он добавлен). И вот чего я пытаюсь достичь в своем контроллере Stimulusmap-controller.js:

import { Controller } from "stimulus";

import EsriMap from "esri/Map";
import MapView from "esri/views/MapView";

export default class extends Controller {
    connect() {
        var map = new EsriMap({
            basemap: "topo-vector"
        });

        var view = new MapView({
            container: this.element,
            map: map,
            center: [-118.80500, 34.02700], // longitude, latitude
            zoom: 13
        });
    }
}

Первоначально я пытался добавить ArcGIS, чтобы он был построен с использованием Webpack (чтобы приведенный выше код работал). Однако я столкнулся с проблемой совместимости между ArcGIS javascript api и tailwindcss. ArcGIS не скомпилировался из-за проблемы с вызовомrequire('fs').

Вместо того, чтобы обходить require('fs')проблема (которая выходит за рамки моего опыта), я решил загрузить ArcGIS js через CDN. Поэтому я попытался настроить ArcGIS с помощьюexternalconfig в Webpack. Вот мойwebpack.js.config файл:

const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const bundleFileName = 'holly';
const dirName = 'Holly/wwwroot/dist';

module.exports = (env, argv) => {
    return {
        mode: argv.mode === "production" ? "production" : "development",
        externals: {
            'esrimap': 'esri/Map',
            'mapview': 'esri/views/MapView'
        },
        entry: ['./Holly/wwwroot/js/app.js', './Holly/wwwroot/css/styles.css'],
        output: {
            filename: bundleFileName + '.js',
            path: path.resolve(__dirname, dirName),
            libraryTarget: "umd"
        },
        module: {
            rules: [{
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'postcss-loader'
                ]
            }]
        },
        plugins: [
            new MiniCssExtractPlugin({
                filename: bundleFileName + '.css'
            })
        ]
    };
};

И вот что я сделал в своем контроллере стимулов:

import { Controller } from "stimulus";

import EsriMap from "esrimap";
import MapView from "mapview";

export default class extends Controller {
    connect() {
        var map = new EsriMap({
            basemap: "topo-vector"
        });

        var view = new MapView({
            container: this.element,
            map: map,
            center: [-118.80500, 34.02700], // longitude, latitude
            zoom: 13
        });
    }
}

Однако в браузере я вижу следующее исключение:

TypeError: esrimap__WEBPACK_IMPORTED_MODULE_1___default.a is not a constructor
    at Controller.connect (map_controller.js:14)
    at Context.connect (context.ts:35)
    at Module.connectContextForScope (module.ts:40)
    at eval (router.ts:109)
    at Array.forEach (<anonymous>)
    at Router.connectModule (router.ts:109)
    at Router.loadDefinition (router.ts:60)
    at eval (application.ts:51)
    at Array.forEach (<anonymous>)
    at Application.load (application.ts:51)

Как вы уже догадались, мои знания javascript/webpack достигли предела. Я провел небольшое исследование ArcGIS javascript API и поддерживает ли он commonjs. Судя по всему, он использует Dojo, который поддерживает AMD. Поэтому вместо этого я попробовал следующую конфигурацию:

        externals: [{
            'esrimap': {
                commonjs: 'esri/Map',
                commonjs2: 'esri/Map',
                amd: 'esri/Map'
            },
            'mapview': {
                commonjs: 'esri/views/MapView',
                commonjs2: 'esri/views/MapView',
                amd: 'esri/views/MapView'
            }
        }],

Но я получаю ту же ошибку. Я прочитал в WebPack документации - это не для меня ясно, как я должен настроить это. Я делаю что-то в корне не так?

2 ответа

Решение

Поэтому я подумал, что вместо этого решу проблему с помощью Stimulus.

Ниже следует ответ на вопрос, предшествующий фразе выше, без учета всего, что следует за ним. Альтернативное решение, которое должно работать, даже если оно не является ответом на весь ваш вопрос.

Создайте скрипт, посвященный вашему компоненту Blazor, и отрендерите его или сделайте ссылку на него в своем Pages/_host.cshtml или wwwroot/index.html:

<script>
    window.myComponent = { 
      init: function(options) { 
        require([
          "esri/Map",
          "esri/views/MapView"
        ], function(Map, MapView) {

          var map = new Map({
            basemap: "topo-vector"
          });

          var view = new MapView({
            container: options.containerId,
            map: map,
            center: [-118.71511,34.09042],
            zoom: 11
          });

        }); // end require

       } // end init

     }; // end myComponent
  </script>

И вызовите этот сценарий в своем компоненте, переопределив async Task OnAfterRenderAsync(bool isFirstRender). Назови это только еслиisFirstRender установлен на true.

Вы можете вызвать сценарий, введя IJSRuntime и вызвав await InvokeAsync("myComponent.init", "container-id")

Что-то вроде этого (непроверено)

@inject IJSRuntime JSRuntime
<div id="@containerId" data-controller="map"></div>

@code {
    private string containerId = Guid.CreateGuid().ToString("n");
    protected override bool ShouldRender()
    {
        var allowRefresh = false;

        return allowRefresh;
    }

    protected override async Task OnAfterRenderAsync(bool isFirstRender) {
      if (isFirstRender){ 
         await JSRuntime.InvokeAsync("myComponent.init", new { containerId: containerId }) 
      }
    }
}

Я думал, что задокументирую свое альтернативное решение с помощью Webpack. В Документах момент вы к использованию машинописи. Я хотел использовать Webpack с ES6, вот зависимости в моемpackage.json файл:

{
  "dependencies": {
    "postcss-import": "^12.0.1",
    "stimulus": "^1.1.1",
    "tailwindcss": "^1.1.4"
  },
  "devDependencies": {
    "@arcgis/webpack-plugin": "^4.14.0",
    "@babel/core": "^7.7.7",
    "@babel/plugin-proposal-class-properties": "^7.7.4",
    "@babel/preset-env": "^7.7.7",
    "@fullhuman/postcss-purgecss": "^1.3.0",
    "@tailwindcss/custom-forms": "^0.2.1",
    "babel-loader": "^8.0.6",
    "cache-loader": "^4.1.0",
    "css-loader": "^3.4.1",
    "mini-css-extract-plugin": "^0.8.2",
    "postcss-loader": "^3.0.0",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  },
}

И вот мой webpack.config.js файл полностью:

const ArcGISPlugin = require("@arcgis/webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const path = require('path');
const bundleFileName = 'holly';
const dirName = 'Holly/wwwroot/dist';

module.exports = (env, argv) => {
    return {
        mode: argv.mode === "production" ? "production" : "development",
        entry: ['./Holly/wwwroot/js/app.js', './Holly/wwwroot/css/styles.css'],
        output: {
            filename: bundleFileName + '.js',
            path: path.resolve(__dirname, dirName),
            publicPath: '/dist/'
        },
        module: {
            rules: [{
                test: /\.css$/,
                use: [
                    "cache-loader",
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'postcss-loader'
                ]
            },
            {
              test: /\.js$/,
              exclude: /node_modules/,
              use: [ "cache-loader", {
                loader: 'babel-loader',
                options: {
                  presets: [
                    '@babel/preset-env'
                  ],
                  plugins: [
                    '@babel/plugin-proposal-class-properties'
                  ]
                }
              }]
            }
          ]
        },
        resolve: {
          modules: ["node_modules/"],
          extensions: [".js"]
        },
        externals: [
            (context, request, callback) => {
              if (/pe-wasm$/.test(request)) {
                return callback(null, "amd " + request);
              }
              callback();
            }
        ],
        plugins: [
            new ArcGISPlugin(),
            new MiniCssExtractPlugin({
                filename: bundleFileName + '.css'
            })
        ],
        node: {
          process: false,
          global: false,
          fs: "empty"
        }
    };
};

Стоит отметить пару моментов. Во-первых, следующее относится к моей исходной проблеме и теперь задокументировано в@arcgis/webpack-plugin readme:

node: {
  process: false,
  global: false,
  fs: "empty"
}

С участием fs: "empty"Я смог перейти к следующей проблеме! Который былpe-wasm- проблема определена (частично) в этом выпуске. Это было здорово, потому что это привело меня к https://github.com/odoe/jsapi-webpack - образцу, написанному на ES6! Во всяком случае, вот где я нашел следующий код. Я не уверен, что именно он делает, но это позволило мне двигаться дальше:

externals: [
    (context, request, callback) => {
      if (/pe-wasm$/.test(request)) {
        return callback(null, "amd " + request);
      }
      callback();
    }
]

Мне также пришлось добавить это:

resolve: {
  modules: ["node_modules/"],
  extensions: [".js"]
}

Это позволило файлам wasm найти недостающие зависимости! Это было фантастически, потому что мой код наконец-то скомпилирован!

Кстати, поскольку я использую Stimulus, я добавил Babel. Стимул требует@babel/plugin-proposal-class-properties.

Во время выполнения мои фрагменты js не загружались. Я наткнулся на следующее решение:

output: {
    filename: bundleFileName + '.js',
    path: path.resolve(__dirname, dirName),
    publicPath: '/dist/'
}

Мне нужно было добавить publicPath: '/dist/'- подпапка, в которой создавались файлы чанков js. В основном это моя вина - я давно настроил эту подпапку, не зная, что это последствие.

Наконец, вот мой обновленный контроллер Stimulus. Мне не хватало ссылки на файл конфигурации esri:

import { Controller } from "stimulus";

import "../esri.config";

import EsriMap from "esri/Map";
import MapView from "esri/views/MapView";

export default class extends Controller {
    connect() {
        var map = new EsriMap({
            basemap: "topo-vector"
        });

        var view = new MapView({
            container: this.element,
            map: map,
            center: [-118.80500, 34.02700], // longitude, latitude
            zoom: 13
        });
    }
}

Это версия конфигурационного файла для ES6, который находится в документации:

import esriConfig from "esri/config";

const DEFAULT_WORKER_URL = "https://js.arcgis.com/4.14/";
const DEFAULT_LOADER_URL = `${DEFAULT_WORKER_URL}dojo/dojo-lite.js`;

esriConfig.workers.loaderUrl = DEFAULT_LOADER_URL;
esriConfig.workers.loaderConfig = {
  baseUrl: `${DEFAULT_WORKER_URL}dojo`,
  packages: [
    { name: "esri", location: DEFAULT_WORKER_URL + "esri" },
    { name: "dojo", location: DEFAULT_WORKER_URL + "dojo" },
    { name: "dojox", location: DEFAULT_WORKER_URL + "dojox" },
    { name: "dijit", location: DEFAULT_WORKER_URL + "dijit" },
    { name: "dstore", location: DEFAULT_WORKER_URL + "dstore" },
    { name: "moment", location: DEFAULT_WORKER_URL + "moment" },
    { name: "@dojo", location: DEFAULT_WORKER_URL + "@dojo" },
    {
      name: "cldrjs",
      location: DEFAULT_WORKER_URL + "cldrjs",
      main: "dist/cldr"
    },
    {
      name: "globalize",
      location: DEFAULT_WORKER_URL + "globalize",
      main: "dist/globalize"
    },
    {
      name: "maquette",
      location: DEFAULT_WORKER_URL + "maquette",
      main: "dist/maquette.umd"
    },
    {
      name: "maquette-css-transitions",
      location: DEFAULT_WORKER_URL + "maquette-css-transitions",
      main: "dist/maquette-css-transitions.umd"
    },
    {
      name: "maquette-jsx",
      location: DEFAULT_WORKER_URL + "maquette-jsx",
      main: "dist/maquette-jsx.umd"
    },
    { name: "tslib", location: DEFAULT_WORKER_URL + "tslib", main: "tslib" }
  ]
};

Я думаю, что это все. Надеюсь, это кому-нибудь пригодится!

Другие вопросы по тегам