Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to only load Zone.js when it is needed

I'm creating an Angular 2 application that can be dynamically injected into a running website. The point of this app is to be able to modify the website content visually.

Everything works as expected when the website in question does not also run using Angular 2. In particular, when the original website uses Angular 2 and Zone.js, my application also tries to load Zone.js but it crashes on error: Zone already loaded. I'm using Webpack as a build system and I tried to solve the problem by splitting the build into 3 parts: manifest, polyfill, and app. Manifest contains only the Webpack manifest, polyfill contains core-js and zone.js, and the rest is in the app. There's also a fourth chunk called index. This is the one placed into the website and it first checks if window.Zone is defined. If it is, only manifest and app are added. Otherwise also polyfill.

The problem is that when polyfill chunk is not loaded, all modules in that chunk are missing from Webpack. So when the code reaches some import from the app chunk that requires either core-js or Zone.js (that's what I think is happening), I get this error:

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)

Somewhere in the manifest file:

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

QUESTION

How do I configure Webpack or Angular to not import zone.js as a dependency but to use that one already registered on the window? I want to be able to conditionally load core-js and zone.js only if it's not yet loaded on the website.

UPDATE

I modified my build (Webpack) to only use a single bundle. In that bundle, I tried using Webpack's dynamic imports like this:

// 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'));

}

I'm using Typescript and that requires definition files. The problem is that Zone.js is an ambient dependency so I get an error about missing definition files when I use it like this. How do I solve this?

like image 558
martindzejky Avatar asked Aug 18 '17 10:08

martindzejky


2 Answers

If you don't want to load zone.js into your bundle, simply remove the import from polyfills.ts:

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

If you want to check the existence of Zone.js in runtime and decide based on the result use this:

declare var System;
if (!window['Zone']) {
    System.import('zone.js/dist/zone');  // Included with Angular CLI.
}
like image 67
Max Koretskyi Avatar answered Sep 19 '22 21:09

Max Koretskyi


I had to put together the accepted answer and some of its comments to get this to work in tsc 2.8.1, so here's everything in one:

First, make sure that tsconfig.app.json has module version that supports dynamic loading:

"module": "esnext" 

Second, prevent the automatic import of zone.js in the polyfills.ts by commenting out this line:

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

Third, add the following in the 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));
}
like image 40
Dr. Hilarius Avatar answered Sep 20 '22 21:09

Dr. Hilarius