【问题标题】:How to only load Zone.js when it is needed如何仅在需要时加载 Zone.js
【发布时间】:2017-08-18 10:32:31
【问题描述】:

我正在创建一个可以动态注入到正在运行的网站的 Angular 2 应用程序。这个应用程序的重点是能够直观地修改网站内容。

当有问题的网站也没有使用 Angular 2 运行时,一切都按预期工作。特别是,当原始网站使用 Angular 2 和 Zone.js 时,我的应用程序也尝试加载 Zone.js,但它在 @987654321 上崩溃@。我使用 Webpack 作为构建系统,并尝试通过将构建拆分为 3 个部分来解决问题:manifestpolyfillapp。 Manifest 只包含 Webpack 清单,polyfill 包含core-jszone.js,其余的都在app 中。还有第四个块称为index。这是放置在网站中的一个,它首先检查是否定义了window.Zone。如果是,则仅添加 manifestapp。否则也polyfill

问题是当 polyfill 块没有加载时,该块中的所有模块都从 Webpack 中丢失。因此,当代码从需要core-jsZone.jsapp 块中到达一些import(我认为这是正在发生的事情)时,我会收到此错误:

TypeError: Cannot read property 'call' of undefined
    at __webpack_require__ (manifest.js:55)
    at Object.<anonymous> (app.js:15016)
    at __webpack_require__ (manifest.js:55)
    at Object.<anonymous> (app.js:65567)
    at __webpack_require__ (manifest.js:55)
    at Object.<anonymous> (app.js:65163)
    at __webpack_require__ (manifest.js:55)
    at Object.defineProperty.value (app.js:65137)
    at __webpack_require__ (manifest.js:55)
    at webpackJsonpCallback (manifest.js:26)

清单文件中的某处:

/******/   // Execute the module function
/******/   modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

问题

如何将 Webpack 或 Angular 配置为不导入 zone.js 作为依赖项,而是使用已在窗口上注册的那个?我希望能够有条件地加载 core-jszone.js,前提是它尚未加载到网站上。

更新

我修改了我的构建 (Webpack) 以仅使用一个包。在那个包中,我尝试像这样使用 Webpack 的 dynamic imports

// import reflect metadata shim
import 'core-js/es6/reflect';
import 'core-js/es7/reflect';


// Angular and application imports
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
import { ViewEncapsulation } from '@angular/core';


// load the editor in the next frame
requestAnimationFrame(() => {

    if (window.Zone) {
        bootstrap();
    }
    else {
        import('zone.js/dist/zone') // <-- PROBLEM HERE
            .then(bootstrap);
    }

});


// bootstraps the editor
function bootstrap() {

    // add the root component to the DOM
    const root = document.createElement('efe-root');
    document.body.appendChild(root);

    // bootstrap the Angular app
    platformBrowserDynamic()
        .bootstrapModule(AppModule, {
            defaultEncapsulation: ViewEncapsulation.Native
        })
        .then(() => console.debug('Exponea Free Editor has bootstrapped'));

}

我正在使用 Typescript,这需要定义文件。问题是Zone.js is an ambient dependency 所以当我像这样使用它时,我得到一个关于缺少定义文件的错误。我该如何解决?

【问题讨论】:

    标签: angular typescript webpack polyfills zonejs


    【解决方案1】:

    如果您不想将 zone.js 加载到您的包中,只需从 polyfills.ts 中删除导入:

    import 'core-js/es6/reflect';
    import 'core-js/es7/reflect';
    import 'zone.js/dist/zone';  <---- remove this line
    

    如果你想在运行时检查Zone.js 的存在并根据结果决定使用这个:

    declare var System;
    if (!window['Zone']) {
        System.import('zone.js/dist/zone');  // Included with Angular CLI.
    }
    

    【讨论】:

    • 重点是我要根据网站是否已经加载来决定是否加载。查看更新后的问题。
    • @chuckeles,更新了我的答案
    • 我忘了提到我使用的是 Typescript,这就是问题所在。正如我在更新的问题中提到的(顺便说一句,它不是System.import,现在只是import),问题是Typescript 缺少该导入的定义文件。
    • @chuckeles,我提供的答案适用于 TypeScript,我检查了。这就是我在那里添加declare var System; 的原因。 _ 只是 import now_ - 是的,但还没有 TS 版本支持 import 调用
    • 在你的回答中你没有提到在哪里写declare var System; if (!window['Zone']) { System.import('zone.js/dist/zone'); // Included with Angular CLI. } 所以我很困惑,但我在 polyfills.ts 中更新了它,它解决了我的问题。谢谢。
    【解决方案2】:

    我必须把接受的答案和它的一些 cmets 放在一起才能让它在 tsc 2.8.1 中工作,所以这里的一切合二为一:

    首先,确保 tsconfig.app.json 有支持动态加载的模块版本:

    "module": "esnext" 
    

    其次,通过注释掉这一行来防止在polyfills.ts中自动导入zone.js:

    // import 'zone.js/dist/zone';  // Included with Angular CLI.
    

    第三,在main.ts中添加以下内容:

    // if the zone has already been loaded, go ahead an bootstrap the app
    if (window['Zone']) {
        bootstrap();
    
    // otherwise, wait to bootstrap the app until zone.js is imported
    } else {
        import('zone.js/dist/zone')
            .then(() => bootstrap());
    }
    
    function bootstrap() {
        platformBrowserDynamic().bootstrapModule(AppModule)
            .catch(err => console.log(err));
    }
    

    【讨论】:

    • 是的,基本上你使用的是动态导入而不是System.import,否则是相同的解决方案。不错!
    【解决方案3】:

    另一种方法是替换 polyfills.ts 中的以下语句:

    import 'zone.js/dist/zone';  // Included with Angular CLI.
    

    与:

    declare var require: any
    if (!window['Zone']) {
      require('zone.js/dist/zone'); // Included with Angular CLI.
    }
    

    注意:您可以在所有导入语句的顶部添加require 的声明。这样它就可以在整个文件中使用。

    【讨论】:

      【解决方案4】:

      我遇到了同样的情况,将我的构建系统从 commonjs / webpack 更改为 systemjs 构建和运行系统

      在 systemjs 中,您可以加载单个文件并配置获取该文件的位置。

      在多个技术、多个团队在单页面架构中将代码推送到同一页面的环境中,systemjs 给了我很好的体验。

      【讨论】:

        【解决方案5】:

        几个月后,这是我的解决方案和这个问题的故事。基本上,很长一段时间我都在我的项目中使用了accepted answer's solution,它工作得很好。它可以检测是否需要 Zone 并仅在尚未加载时才加载它。

        但是,当底层应用程序加载缓慢并且我的应用程序会在底层应用程序之前引导时,就会出现问题。在这种情况下,我的应用程序会认为 Zone 没有加载并加载它,只是在它不断加载时与底层应用程序发生冲突。

        还有其他问题,特别与这个问题无关。例如,我发现 Angular 与 Prototype 发生冲突,很多网站都使用 Prototype!

        总而言之,我不得不完全改变我的方法。我将我的应用程序移到了iframe,它可以访问父网站但不继承其 CSS 或 JS 范围。这样我就可以安全地加载我的应用程序、Angular 及其所有依赖项,而不必担心某些东西可能会破坏底层网站/应用程序。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-02-24
          • 2013-11-30
          • 2014-10-31
          • 2020-09-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多