Search code examples
angularwebpackmicro-frontendwebpack-externalssingle-spa-angular

Angular as Webpack External


I'm currently trying to lower build times by externalizing Angular dependencies through Webpack externals. So far, I have achieved this wihout problems for React and other minor libraries.

If I just move '@angular/compiler' it also works, however when externalizing '@angular/core' and the others I receive a Can't resolve all parameters for ApplicationModule: (?) at at syntaxError (util.ts:100) at CompileMetadataResolver._getDependenciesMetadata (metadata_resolver.ts:957) ... error when bootstrapping the app.

The files that I'm using are the 8.2.14 UMD bundles you can find in unpkg.com and my externals are:

[
    /^@example\, 
    /^rxjs$/, 
    /^rxjs\/operators$/, 
    /^@angular\/core$/, 
    /^@angular\/compiler$/, 
    /^@angular\/common$/, 
    /^@angular\/common\/http$/, 
    /^@angular\/forms$/, 
    /^@angular\/platform-browser$/, 
    /^@angular\/platform-browser-dynamic$/, 
    /^@angular\/router$/, 
    /^zone\.js$/,
    /^single-spa$/
]

The rxjs bundles are the ones found here https://cdn.jsdelivr.net/npm/@esm-bundle.

You can see the error on this repo: https://github.com/OriolInvernonLlaneza/test-share-angular

Thanks in advance!

Edit: Thanks to yurzui's answer this error was solved. However, I now receive another error:

Class constructor I cannot be invoked without 'new'
    at new EventEmitter (event_emitter.ts:78)
    at new NgZone (ng_zone.ts:97)
    at getNgZone (application_ref.ts:358)
    at PlatformRef.bootstrapModuleFactory (application_ref.ts:255)
    at application_ref.ts:308

Solution

  • You need to include core-js shim library so that Angular code can use Reflect.getOwnMetadata method to read metadata from decorated classes

    index.ejs

    <% if (isLocal) { %>
      <script src="https://unpkg.com/[email protected]/client/shim.min.js"></script> <== add this
      <script type="systemjs-importmap">
           ....
    

    It doesn't matter which core-js version you will use but it should contain polyfill for reflect.get-own-metadata.

    For example you can also use <script src='https://unpkg.com/[email protected]/minified.js'></script>

    Angular CLI adds this polyfill by default in polyfills.ts for jit. But single-spa-angular removes this polyfill.

    Note that you should also include zone.js to your file so the complete code should look like

    index.ejs

    <script src='https://unpkg.com/[email protected]/minified.js'></script>
    <script src="https://unpkg.com/zone.js"></script>
    

    Update

    The error Class constructor I cannot be invoked without 'new' comes from rxjs bundle(@esm-bundle/[email protected]/system/rxjs.min.js) which means that this bundle wasn't built properly for using it in browser(more on this error here Javascript ES6 TypeError: Class constructor Client cannot be invoked without 'new').

    You can use umd bundle for rxjs

    "rxjs": "https://cdn.jsdelivr.net/npm/[email protected]/bundles/rxjs.umd.min.js",
    "rxjs/operators": "https://cdn.jsdelivr.net/npm/@esm-bundle/[email protected]/system/rxjs-operators.min.js",
    

    I will share the whole inject.ejs file below

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Root Config</title>
      <meta name="importmap-type" content="systemjs-importmap" />
      <% if (isLocal) { %>
        <!--<script src="https://unpkg.com/[email protected]/client/shim.min.js"></script>-->
      <script type="systemjs-importmap">
        {
          "imports": {
            "@example/root-config": "http://localhost:9000/root-config.js",
            "@example/angular": "http://localhost:4200/main.js",
    
            "single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/5.1.0/system/single-spa.dev.js",
    
            "rxjs": "https://cdn.jsdelivr.net/npm/[email protected]/bundles/rxjs.umd.min.js",
            "rxjs/operators": "https://cdn.jsdelivr.net/npm/@esm-bundle/[email protected]/system/rxjs-operators.min.js",
    
            "@angular/core": "https://unpkg.com/@angular/[email protected]/bundles/core.umd.js",
            "@angular/compiler": "https://unpkg.com/@angular/[email protected]/bundles/compiler.umd.js", 
            "@angular/common": "https://unpkg.com/@angular/[email protected]/bundles/common.umd.min.js",
            "@angular/common/http": "https://unpkg.com/@angular/[email protected]/bundles/common-http.umd.min.js",
    
            "@angular/platform-browser-dynamic": "https://unpkg.com/@angular/[email protected]/bundles/platform-browser-dynamic.umd.min.js",
            "@angular/platform-browser": "https://unpkg.com/@angular/[email protected]/bundles/platform-browser.umd.min.js",
            "@angular/platform-browser/animations": "https://unpkg.com/@angular/[email protected]/bundles/platform-browser-animations.umd.min.js",
    
            "@angular/forms": "https://unpkg.com/@angular/[email protected]/bundles/forms.umd.min.js",
            "@angular/router": "https://unpkg.com/@angular/[email protected]/bundles/router.umd.min.js",
            "@angular/animations": "https://unpkg.com/@angular/[email protected]/bundles/animations.umd.js",
            "@angular/animations/browser": "https://unpkg.com/@angular/[email protected]/bundles/animations-browser.umd.min.js",
    
            "tslib": "https://cdnjs.cloudflare.com/ajax/libs/tslib/1.10.0/tslib.min.js",
            "zone.js": "https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.9.1/zone.min.js"
          }
        }
      </script>
      <% } %>
      <script src='https://unpkg.com/[email protected]/minified.js'></script>
      <script src="https://unpkg.com/zone.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/import-map-overrides/dist/import-map-overrides.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/systemjs/dist/system.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/systemjs/dist/extras/amd.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/systemjs/dist/extras/named-exports.min.js"></script>
    </head>
    <body>
      <main id="main"></main>
      <script>
        System.import("@example/root-config");
      </script>
      <import-map-overrides-full show-when-local-storage="devtools" dev-libs></import-map-overrides-full>
    </body>
    </html>