【问题标题】:How to include ArcGIS Javascript API via CDN with Webpack to work with Stimulus?如何通过 CDN 包含 ArcGIS Javascript API 和 Webpack 以使用 Stimulus?
【发布时间】:2020-01-08 10:23:18
【问题描述】:

quick start ArcGIS javascript api 指南中,它具有以下示例代码:

<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 组件中。所以我遇到了第一个绊脚石 - 我不允许在 Blazor 组件中添加 &lt;script&gt; 标记。那是因为可以随时动态创建控件。所以我想我应该用 Stimulus 来解决这个问题。

这是我的 Blazor 组件(到目前为止)。我有一个名为Map.razor的文件:

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

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

        return allowRefresh;
    }
}

我添加了ShouldRender 方法,这样组件将只呈现一次(添加时)。这就是我想要在我的刺激控制器map-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') 问题(这超出了我现有的经验),而是选择通过CDN 引入ArcGIS js。所以我尝试使用 Webpack 中的 external 配置功能设置 ArcGIS。这是我的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 做了一些研究。显然它使用了支持 AMD 的 Dojo。所以我尝试了以下配置:

        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 documentation - 我不清楚我应该如何配置它。我做错了什么?

【问题讨论】:

    标签: webpack cdn blazor arcgis-js-api stimulusjs


    【解决方案1】:

    所以我想我应该用 Stimulus 来解决这个问题。

    以下是对上述短语前面问题的回答,忽略后面的所有内容。即使它不是您完整问题的答案,也应该可以使用的替代解决方案。

    创建一个专用于您的 blazor 组件的脚本,并在您的 Pages/_host.cshtmlwwwroot/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 }) 
          }
        }
    }
    

    【讨论】:

    • 谢谢。这很有帮助。我从 Stimulus 控制器中提到了 window.myComponent 的变体 - 这让我可以继续按下
    • 但是,我仍然想知道是否可以通过 Webpack 使用 import 语句加载 ArcGIS
    • 我现在对 Webpack 有了更多了解。仅当通过 Webpack 包含 ArcGIS 时,导入才相关 - 因为 @arcgis/webpack-plugin 确实具有将 CommonJS 调用转换为 AMD 的魔力(因此调用保持异步)。 CDN 引用没有这种魔力,只支持 AMD。这意味着这个最好的答案
    【解决方案2】:

    我想我会用 Webpack 记录我的替代解决方案。 docs 指向您使用 Typescript。我想在 ES6 中使用 Webpack,这是我的 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-pluginreadme

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

    有了fs: "empty",我就可以继续下一个问题了!这是pe-wasm - 这个问题(部分)在这个issue 中定义。这太棒了,因为它把我带到了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
            });
        }
    }
    

    这是 docs 中配置文件的 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" }
      ]
    };
    

    我认为这涵盖了所有内容。希望它对某人有用!

    【讨论】:

    • 它很有用,但不是很清楚(至少对我来说)为什么/正在做什么。你反对用 20 个字或更少的字来指定总体战略吗? (感谢您记录您的答案)
    • 所以最初我想从我们的服务器为我们的 ArcGIS 地图提供 Javascript(这就是我避免使用 CDN 链接的原因。我还想使用 ES6 为 ArcGIS Javascript 编写代码。使用 Webpack 部署 ArcGIS Javascript允许我做这两件事
    • 上面列出的步骤主要是关于两件事 - 如何配置 webpack 和解决方法以使 ArcGIS 使用 webpack 模块(而不是 AMD 模块)
    • 但是,使用 webpack 构建 ArcGIS 显着增加了构建/部署时间。上面列出的解决方法很多。最后我决定更容易坚持使用 ES5 以避免延长构建时间
    • 最后一点,我曾打算使用 Stimulus js - 帮助隔离我的 javascript 代码(将 ArcGIS 与 webpack 结合使用的另一个动机)。 (当时)坚持使用 ES5,我最终也放弃了这个想法。现在在现代浏览器中有更多对 ES6 的支持,我可能会重新审视/研究 Stimulus 与 Blazor 的使用
    猜你喜欢
    • 2017-02-08
    • 2022-09-28
    • 1970-01-01
    • 2016-03-10
    • 2020-04-15
    • 1970-01-01
    • 2021-06-27
    • 2017-10-03
    • 2016-07-27
    相关资源
    最近更新 更多