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__);
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.
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?
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.
}
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));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With