在 Ionic 应用程序中使用 Vega Charts 会导致在某些设备上启动时出现运行时错误

Posted

技术标签:

【中文标题】在 Ionic 应用程序中使用 Vega Charts 会导致在某些设备上启动时出现运行时错误【英文标题】:Using Vega Charts in an Ionic App causes runtime errors in launching on some devices 【发布时间】:2020-04-23 15:54:27 【问题描述】:

令我非常懊恼的是,我发现我在 android (8.0) 手机和 iPhone 上成功开发和测试的 Ionic 4 应用程序在 Android (8.1 ) 平板电脑和在 iPad 上启动时崩溃。使用adb logcat 诊断技术,我观察到在错误的Android 平板电脑上,vendor-es5.js 中报告了一个语法错误,当我挖掘我的项目的 www 文件夹并转到错误的引用行时,它说SyntaxError: Unexpected token *,我得到的代码显然来自 node_modules/d3-delaunay/src/delaunay.js,并且使用了 es6 求幂运算符 **,具体来说:

r = 1e-8 * Math.sqrt((bounds[3] - bounds[1])**2 + (bounds[2] - bounds[0])**2);

我不知道为什么这段代码在一些 设备上会出现问题,也不知道是什么导致了这段代码,它不是 es5 (?) 最终出现在 vendor-es5.js 文件中没有被适当地转译。为了更进一步,我手动破解了 delaunay.js 文件,将所有求幂实例替换为 Math.pow() 的等效用法,果然,运行时更进一步,但最终在来自的函数中再次搁浅node_modules/vega-dataflow/src/dataflow/load.js 并抱怨 SyntaxError: Unexpected token function,特别是在这一行:

export async function request(url, format) 

同样,显然 async/await 不是 es5 构造,那么为什么它会以 vendor-es5.js 结尾。在这一点上,我觉得这里出现了系统性错误,除了切换图形库之外,我还没有能力理解如何克服它?如果可能的话,我想避免这种情况,所以我的问题是:

    为什么会这样? 为什么它只影响部分设备,而不是所有设备? 有没有一种方法可以在不切换到其他图形库的情况下解决该问题?

更新 #1

因为它是一个 Ionic4 项目,这意味着它是一个 Angular 8 项目,这意味着它是一个 Webpack 项目(就像平台的默认设置一样)。这是我的angular.json 文件:


  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "defaultProject": "app",
  "newProjectRoot": "projects",
  "projects": 
    "app": 
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": ,
      "architect": 
        "build": 
          "builder": "@angular-devkit/build-angular:browser",
          "options": 
            "outputPath": "www",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "assets": [
              
                "glob": "**/*",
                "input": "src/assets",
                "output": "assets"
              ,
              
                "glob": "**/*.svg",
                "input": "node_modules/ionicons/dist/ionicons/svg",
                "output": "./svg"
              
            ],
            "styles": [
              
                "input": "src/theme/variables.scss"
              ,
              
                "input": "src/global.scss"
              
            ],
            "scripts": []
          ,
          "configurations": 
            "production": 
              "fileReplacements": [
                
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                
              ]
            ,
            "ci": 
              "progress": false
            
          
        ,
        "serve": 
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": 
            "browserTarget": "app:build"
          ,
          "configurations": 
            "production": 
              "browserTarget": "app:build:production"
            ,
            "ci": 
              "progress": false
            
          
        ,
        "extract-i18n": 
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": 
            "browserTarget": "app:build"
          
        ,
        "test": 
          "builder": "@angular-devkit/build-angular:karma",
          "options": 
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "styles": [],
            "scripts": [],
            "assets": [
              
                "glob": "favicon.ico",
                "input": "src/",
                "output": "/"
              ,
              
                "glob": "**/*",
                "input": "src/assets",
                "output": "/assets"
              
            ]
          ,
          "configurations": 
            "ci": 
              "progress": false,
              "watch": false
            
          
        ,
        "lint": 
          "builder": "@angular-devkit/build-angular:tslint",
          "options": 
            "tsConfig": [
              "tsconfig.app.json",
              "tsconfig.spec.json",
              "e2e/tsconfig.json"
            ],
            "exclude": ["**/node_modules/**"]
          
        ,
        "e2e": 
          "builder": "@angular-devkit/build-angular:protractor",
          "options": 
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "app:serve"
          ,
          "configurations": 
            "production": 
              "devServerTarget": "app:serve:production"
            ,
            "ci": 
              "devServerTarget": "app:serve:ci"
            
          
        ,
        "ionic-cordova-build": 
          "builder": "@ionic/angular-toolkit:cordova-build",
          "options": 
            "browserTarget": "app:build"
          ,
          "configurations": 
            "production": 
              "browserTarget": "app:build:production"
            
          
        ,
        "ionic-cordova-serve": 
          "builder": "@ionic/angular-toolkit:cordova-serve",
          "options": 
            "cordovaBuildTarget": "app:ionic-cordova-build",
            "devServerTarget": "app:serve"
          ,
          "configurations": 
            "production": 
              "cordovaBuildTarget": "app:ionic-cordova-build:production",
              "devServerTarget": "app:serve:production"
            
          
        
      
    
  ,
  "cli": 
    "defaultCollection": "@ionic/angular-toolkit"
  ,
  "schematics": 
    "@ionic/angular-toolkit:component": 
      "styleext": "scss"
    ,
    "@ionic/angular-toolkit:page": 
      "styleext": "scss"
    
  

...这是我的package.json 文件的相关子集:


  "dependencies": 
    "@angular/common": "~8.1.2",
    "@angular/core": "~8.1.2",
    "@angular/forms": "~8.1.2",
    "@angular/http": "^7.2.15",
    "@angular/platform-browser": "~8.1.2",
    "@angular/platform-browser-dynamic": "~8.1.2",
    "@angular/router": "~8.1.2",
    "@ionic-native/core": "^5.15.1",
    "@ionic/angular": "^4.7.1",
    "vega": "~5.6.0",
    "vega-lite": "^3.4.0",
    "vega-themes": "^2.4.0",
    "zone.js": "~0.9.1"
  ,
  "devDependencies": 
    "@angular-devkit/architect": "~0.801.2",
    "@angular-devkit/build-angular": "~0.801.2",
    "@angular-devkit/core": "~8.1.2",
    "@angular-devkit/schematics": "~8.1.2",
    "@angular/cli": "~8.1.2",
    "@angular/compiler": "~8.1.2",
    "@angular/compiler-cli": "~8.1.2",
    "@angular/language-service": "~8.1.2",
    "@ionic/angular-toolkit": "~2.0.0",
    "@types/jasmine": "~3.3.8",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "~8.9.4",
    "codelyzer": "^5.0.0",
    "jasmine-core": "~3.4.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~4.1.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "~2.0.1",
    "karma-jasmine-html-reporter": "^1.4.0",
    "protractor": "~5.4.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.15.0",
    "typescript": "~3.4.3"
  

更新 #2

继续尝试解决这个问题,我对package.json 进行了以下一组更新:

  "dependences": 
    "tslib": added => "^1.10.0" 
    "vega": "~5.6.0" => "^5.9.0"
    "vega-lite": "^3.4.0" => "^4.0.2"

  "devDependencies": 
    "@angular/compiler": "~8.1.2" => "~8.2.9"
    "@angular/compiler-cli": "~8.1.2" => "~8.2.9"
    "typescript": "~3.4.3" => "~3.5.3"

...通过这些更改,我想我在 www/vendor-es5.js 文件中得到了明显的 es5 编译输出,而我的 adb logcat 结果似乎没有表明语法错误。不幸的是,该应用仍然无法通过启动画面(同样,只有 一些 设备会出现这种情况)。

这是我在项目中的tsconfig.json 文件:


  "compileOnSave": false,
  "compilerOptions": 
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "module": "esnext",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "importHelpers": true,
    "target": "es2015",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "dom"
    ]
  ,
  "angularCompilerOptions": 
    "fullTemplateTypeCheck": true,
    "strictInjectionParameters": true
  

...就vega的用法而言,其症结在于:

    const theme = vega.fivethirtyeight;
    this._view = new vega.View(vega.parse(vegaSpec, theme), )
      .initialize(this.container.nativeElement)
      .logLevel(vega.Warn)
      .renderer('svg');

...如果我将adb logcat 输出过滤到E(错误)行,在有问题的设备上,我会看到:

01-10 09:17:27.650  6413  6413 E ApkAssets: Error while loading asset assets/natives_blob_64.bin: java.io.FileNotFoundException: assets/natives_blob_64.bin
01-10 09:17:27.651  6413  6413 E ApkAssets: Error while loading asset assets/snapshot_blob_64.bin: java.io.FileNotFoundException: assets/snapshot_blob_64.bin
01-10 09:17:27.680  6413  6413 E         : appName=xxxxxx, acAppName=/system/bin/surfaceflinger
01-10 09:17:27.680  6413  6413 E         : 0
01-10 09:17:27.683  6413  6413 E         : appName=xxxxxx, acAppName=vStudio.Android.Camera360
01-10 09:17:27.683  6413  6413 E         : 0
01-10 09:17:27.781  6413  6413 E MPlugin : Unsupported class: com.mediatek.common.telephony.IOnlyOwnerSimSupport
01-10 09:17:28.153  6413  6464 E libEGL  : validate_display:99 error 3008 (EGL_BAD_DISPLAY)
01-10 09:17:28.432  6413  6464 E         : appName=xxxxxx, acAppName=vStudio.Android.Camera360
01-10 09:17:28.433  6413  6464 E         : 0
01-10 09:17:28.436  6413  6464 E         : appName=xxxxxx, acAppName=vStudio.Android.Camera360
01-10 09:17:28.436  6413  6464 E         : 0
01-10 09:17:28.437  6413  6464 E         : appName=xxxxxx, acAppName=vStudio.Android.Camera360
01-10 09:17:28.437  6413  6464 E         : 0
01-10 09:17:30.514  6413  6455 E         : appName=xxxxxx, acAppName=vStudio.Android.Camera360
01-10 09:17:30.514  6413  6455 E         : 0
01-10 09:17:30.515  6413  6455 E         : app

...这里是W(警告)行:

01-10 09:17:27.835  6413  6413 W chromium: [WARNING:password_handler.cc(33)] create-->contents = 0x9c66ec00, delegate = 0xa4b7edd0
01-10 09:17:27.835  6413  6413 W chromium: [WARNING:password_handler.cc(41)] attaching to web_contents 
01-10 09:17:27.837  6413  6413 W cr_AwContents: onDetachedFromWindow called when already detached. Ignoring
01-10 09:17:28.185  6413  6455 W libEGL  : [ANDROID_RECORDABLE] format: 1
01-10 09:17:28.209  6413  6464 W VideoCapabilities: Unrecognized profile/level 1/32 for video/mp4v-es
01-10 09:17:28.209  6413  6464 W VideoCapabilities: Unrecognized profile/level 32768/2 for video/mp4v-es
01-10 09:17:28.209  6413  6464 W VideoCapabilities: Unrecognized profile/level 32768/64 for video/mp4v-es
01-10 09:17:28.244  6413  6455 W libEGL  : [ANDROID_RECORDABLE] format: 1
01-10 09:17:28.248  6413  6464 W VideoCapabilities: Unsupported mime video/x-ms-wmv
01-10 09:17:28.253  6413  6464 W VideoCapabilities: Unsupported mime video/divx
01-10 09:17:28.262  6413  6464 W VideoCapabilities: Unsupported mime video/xvid
01-10 09:17:28.268  6413  6464 W VideoCapabilities: Unsupported mime video/flv1
01-10 09:17:28.274  6413  6464 W VideoCapabilities: Unrecognized profile/level 1/32 for video/mp4v-es
01-10 09:17:28.485  6413  6413 W cr_BindingManager: Cannot call determinedVisibility() - never saw a connection for the pid: 6413
01-10 09:17:28.568  6413  6413 W cr_BindingManager: Cannot call determinedVisibility() - never saw a connection for the pid: 6413

【问题讨论】:

请出示您的tsconfig.json 和一些带有vega 用法的代码。我想我可以在这里提供帮助。看起来你只需要强制 angular 转译外部库。 我还发现 vega 掉线了 es5 support github.com/vega/vega/issues/1470#issuecomment-444951182 @nickbullock 在等待 S/O 社区的帮助期间,我一直在努力解决这个问题...我将很快发布更新,其中包括 tsconfig.json 并添加一些有关 vega 使用的详细信息 @nickbullock 我已根据您的建议更新了问题,并提供了更多详细信息 【参考方案1】:

首先我想说这真的是vega 包错误 - 我认为通过 npm 传递未编译的代码是一种糟糕的方式。例如Angular Package Format 保证您将获得 es5 有效代码,如果您需要它。但是vega 不是一个明确的angular 依赖,所以让我们解决它。

为什么会这样?

因为一些开发人员以es6+ 标准交付软件包,并且在您需要es5 兼容应用程序之前是可以的。在我看来,库开发人员应该构建和交付 es5es6 包,否则他们的用户会很头疼(就像你使用 vega 的情况一样)。

为什么它只影响部分设备,而不是所有设备?

老实说,我在原生移动开发方面的经验非常有限 - 我在这里只能说,例如移动 Chrome 和桌面 Chrome 在它们的引擎上存在一些差异。这意味着不能保证使用相同的软件会提供相同的结果。有时您可以在移动浏览器中找到该错误,而无法在桌面浏览器中重现它。

我认为在您的情况下,某些带有某些浏览器引擎的设备可以使用 es6 代码 - 而有些则不能。 在您的问题的第一个版本中,还有用户代理字符串 - 我认为高级移动开发人员可以说比我更多。

有没有一种方法可以在不切换到其他图形库的情况下解决该问题?

是的。 我创建了一个repo,其设置与您的非常相似——基于angular@8 的简单ionic@4 项目。

您的捆绑包现在是 es5es6 混合的。让我们完全兼容 es5 以在任何浏览器中工作(我甚至在 ie11 中测试了这个项目)。 完成工作的步骤:

    安装依赖项。我们将在进一步的步骤中需要它们。
npm i -S regenerator-runtime
npm i -D @angular-builders/custom-webpack babel-loader @babel/core @babel/preset-env
    tsconfig 中的target 属性更改为es5"target": "es5" 我们将转译async/await,所以我们需要将regenerator-runtime polyfill 添加到polyfills.ts 作为import 'regenerator-runtime/runtime' 主要步骤。更改angular.json 中的构建器并添加webpack.config.js 的路径以使用buildserve 的自定义webpack 配置:
       "build": 
          "builder": "@angular-builders/custom-webpack:browser",
          "options": 
            "customWebpackConfig": 
                 "path": "./webpack.config.js"
              ,
...
        "serve": 
          "builder": "@angular-builders/custom-webpack:dev-server",
    在根文件夹中创建webpack.config.js,并使用规则转换 vega 及其依赖项。我以非常迫切的方式找到了它们。
// these dependencies are es6!!!
const transpileList = ['node_modules/vega', 'node_modules/d3', 'node_modules/delaunator'];

module.exports = function(base) 
    return 
        ...base,
        module: 
            ...base.module,
            rules: [
                ...base.module.rules,
                
                    test: function(fileName) 
                        return transpileList.some(name => fileName.includes(name)) && fileName.endsWith('.js');
                    ,
                    use: 
                        loader: 'babel-loader',
                        options: 
                            presets: ['@babel/preset-env']
                        
                    
                
            ]
        
    

完成这些步骤后,我希望您的应用程序能够在任何es5 环境中运行。我在桌面 ie11 和平板电脑 Samsung A 上使用默认三星浏览器进行了尝试。

【讨论】:

只是为了阐明这个or example mobile Chrome and desktop Chrome have some differences in their engines ios 上的任何浏览器都在使用 Safari 引擎 :( 这就是存在差异的原因。 酷,我会试试这个。您所说的“势在必行”是指“经验的”,如反复试验吗?比如你是如何确定 transpileList 中的内容的? @vicatcu 是的,你是对的,我的意思是试错法。我在d3/array 中发现的第一个语法错误。然后在delaunator。然后在d3/delaunayvega-dataflow 中,正如您在问题中描述的那样。我认为这里的一个好的做法是在我的解决方案中使用regexp 或排除列表,而不是指向单个依赖项,因为每次版本更新后vega 包依赖项的数量都会增加。 @nickbullock 我很高兴地报告这解决了问题。太感谢了!我希望有一种比分析文件名更“自动”检测和应用 babel 的方法,但这很有效,所以我不能抱怨! @vicatcu 很高兴听到。我尝试将 webpack 的 rule.issuer 与值 /vega/ 一起使用,但它不能递归地工作(深入到最后一级):( 只有第一级 imports 匹配。

以上是关于在 Ionic 应用程序中使用 Vega Charts 会导致在某些设备上启动时出现运行时错误的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法在 vega 重复图中的 vega 表达式中使用图号/标识符?

在仪表板中使用时间过滤器更改 Kibana 中 Vega 的范围

kali扫描工具--vega

react-vega 和 react-vega 工具提示

Vega-lite 从数据中设置颜色,同时保留图例

在 vega 中刷/链接(不是 vega-lite)