动态加载外部webpack捆绑的ngModule作为路由处理程序 [英] Dynamically loading an external webpack bundled ngModule as a route handler

查看:117
本文介绍了动态加载外部webpack捆绑的ngModule作为路由处理程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们希望将我们的大型前端项目划分为多个单独部署的项目,这些项目更易于使用。我试图包含一个捆绑的ngModule来处理来自另一个应用程序的路由。应用程序必须不知道彼此的配置。捆绑包将通过全局变量共享一些大的依赖项(如Angular)。我们不需要动摇捆绑包,我们可能只需要接受一些重复的依赖关系。

We want to divide our large frontend projects into multiple separately deployed projects which are easier to work with. I am trying to include a bundled ngModule to handle a route from within another app. The apps must be ignorant of each other's configuration. The bundles will share some large dependencies(like Angular) via globals. We don't need to shake across the bundles and we may just have to accept some duplicate dependencies.

根路由器抱怨

Error: No NgModule metadata found for 'TestsetModule'.

这让我相信子模块在加载时没有进行角度编译,或者没有注册模块由于某种原因。我认为可能需要手动编译模块,但我不知道如何使用这个 https ://angular.io/api/core/Compiler#compileModuleAndAllComponentsAsync

which leads me to believe the child module is not being angular compiled on load, or is not registering its module for some reason. I think it may be necessary to manually compile the module, but I'm not sure how to use this https://angular.io/api/core/Compiler#compileModuleAndAllComponentsAsync

根应用程序通过路径加载孩子:

The root app loads the child via a route:

import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const load = require("little-loader");


const routes: Routes = [
  { path: ``, loadChildren: () => new Promise(function (resolve) {
      load('http://localhost:3100/testset-module-bundle.js',(err: any) => {
        console.log('global loaded bundle is: ', (<any>global).TestsetModule )
        resolve((<any>global).TestsetModule)
      }
    )
  })}
];

export const HostRouting: ModuleWithProviders = RouterModule.forRoot(routes);

我也尝试使用角度路由器的字符串解析语法,而不是你看到的这个奇怪的全局事物,但我有类似的问题。

I also tried using angular router's string resolution syntax rather than this weird global thing you see but I had similar issues.

以下是正在加载的模块,除了全局导出外非常标准:

Here is the module which is being loaded, very standard except for the global export:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpModule } from '@angular/http';
//import { MaterialModule } from '@angular/material';
import { FlexLayoutModule } from '@angular/flex-layout';
import { FormsModule }   from '@angular/forms';
import { LoggerModule, Level } from '@churro/ngx-log';

import { FeatureLoggerConfig } from './features/logger/services/feature-logger-config';


import { TestsetComponent } from './features/testset/testset.component';
import { TestsetRouting } from './testset.routing';

@NgModule({
    imports: [
        CommonModule,
        //MaterialModule,
        FlexLayoutModule,
        HttpModule,
        FormsModule,
        LoggerModule.forChild({
          moduleName: 'Testset',
          minLevel: Level.INFO
        }),
        TestsetRouting,
    ],
    declarations: [TestsetComponent],
    providers: [
      /* TODO: Providers go here */
    ]
})
class TestsetModule { }
(<any>global).TestsetModule = TestsetModule

export {TestsetModule as default, TestsetModule};

以下是根捆绑的webpack配置。注意全局导出通过命名不佳的ProvidePlugin。

Here is the webpack configuration of the root bundle. Note the global exports via the poorly named "ProvidePlugin".

const webpack = require('webpack');
const AotPlugin = require('@ngtools/webpack').AotPlugin;
const path = require('path');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');
const IgnorePlugin = require('webpack/lib/IgnorePlugin');
const PolyfillsPlugin = require('webpack-polyfills-plugin');
const WebpackSystemRegister = require('webpack-system-register');


module.exports = (envOptions) => {
    envOptions = envOptions || {};
    const config = {

        entry: {
          'bundle': './root.ts'
        },
        output: {

          libraryTarget: 'umd',
          filename: '[name].js',//"bundle.[hash].js",
          chunkFilename: '[name]-chunk.js',
          path: __dirname
          },
          externals: {

          },
        resolve: {
            extensions: ['.ts', '.js', '.html'],
        },
        module: {
            rules: [
                { test: /\.html$/, loader: 'raw-loader' },
                { test: /\.css$/, loader: 'raw-loader' },

            ]
        },
        devtool: '#source-map',
        plugins: [
          new webpack.ProvidePlugin({
            'angular': '@angular/core',
            'ngrouter': '@angular/router',
            'ngxlog':'@churro/ngx-log'
          })

        ]
    };
    config.module.rules.push(
      { test: /\.ts$/, loaders: [
        'awesome-typescript-loader', 
        'angular-router-loader',
        'angular2-template-loader', 
        'source-map-loader'
      ] } 
    );
  }



    return config;
};

这是子包的webpack配置。注意外部,它将角度视为全局。

And here is the webpack configuration of the child bundle. Note the "externals" which look for angular as a global.

module.exports = (envOptions) => {
    envOptions = envOptions || {};
    const config = {
        entry: {
          'testset-module-bundle': './src/index.ts'
        },
        output: {
          //library: 'TestsetModule',
          libraryTarget: 'umd',
          filename: '[name].js',//"bundle.[hash].js",
          chunkFilename: '[name]-chunk.js',
          path: path.resolve(__dirname, "dist")
          },
          externals: {
            //expect these to come from the app that imported us
            // name to be required : name from global
             'angular': '@angular/core',
             'ngrouter': '@angular/router',
             'ngxlog': '@churro/ngx-log'       
          },
        resolve: {
            extensions: ['.ts', '.js', '.html'],
        },
        module: {
            rules: [
                { test: /\.html$/, loader: 'raw-loader' },
                { test: /\.css$/, loader: 'raw-loader' },

            ]
        },
        devtool: '#source-map',
        plugins: [

        ]
    };

    config.module.rules.push(
      { test: /\.ts$/, loaders: [
        'awesome-typescript-loader', 
        'angular-router-loader',
        'angular2-template-loader', 
        'source-map-loader'
      ] }
    );
  }



    return config;
};

这里有一个很好的衡量标准是我的tsconfig文件'awesome-typescript-loader'读取。

And for good measure here is my tsconfig file which 'awesome-typescript-loader' reads.

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true,
    "baseUrl": ".",
    "rootDir": "src",
    "outDir": "app",
    "paths": {
      "@capone/*": [
        "*"
      ],
      "@angular/*": [
        "node_modules/@angular/*"
      ],
      "rxjs/*": [
        "node_modules/rxjs/*"
      ]
    }
  },

  "exclude": ["node_modules", "src/node_modules", "compiled", "src/dev_wrapper_app"],
  "angularCompilerOptions": {
    "genDir": "./compiled",
    "skipMetadataEmit": true
  }
}

如果你还在读书,真棒。当两个bundle都是同一个webpack配置的一部分并且子模块只是一个块时,我能够使这个工作。 Angular旨在实现这一目标。但我们的用例是让孩子和父母在运行之前彼此无知。

If you're still reading, awesome. I was able to get this working when both bundles are part of the same webpack config and the child module is just a chunk. Angular is designed to do that. But our use case is to have the children and parent be ignorant of each other until runtime.

推荐答案

如你所述


应用程序必须不知道彼此的配置。

The apps must be ignorant of each other's configuration.

我在Angular2中遇到了类似的问题。我通过创建子应用程序解决了它。一个单独的子main.browser.ts和index.html文件。它有自己的依赖项,共享相同的节点模块。两个主要模块都引导不同的app-component。我们正在开发没有angular-cli的Angular。

I had a similar problem in Angular2. I solved it by creating a sub-application. A separate sub-main.browser.ts and index.html file. It had its own dependencies, sharing the same node modules. Both main modules bootstrapping different app-component. We were working on Angular without angular-cli.

在webpack配置中,我添加了

In webpack config, I added

entry: {

  'polyfills': './src/polyfills.browser.ts',
  'main' .   :     './src/main.browser.aot.ts',
  'sub-main' : '/src/sub-main.browser.ts'

},

以及更详细的HtmlWebpackPlugin。在块中,我们只加载将在应用程序中使用的模块。如果我们看到polyfill很常见。

and a more detailed HtmlWebpackPlugin. In the chunks, we load only modules that will be used in both the app. If we see polyfills is common.

   new HtmlWebpackPlugin({
    template: 'src/index.html',
    title: METADATA.title,
    chunksSortMode: 'dependency',
    metadata: METADATA,
    inject: 'head',
    chunks: ['polyfills','main']
  }),

  new HtmlWebpackPlugin({
    template: 'src/index2.html',
    title: 'Sub app',
    chunksSortMode: 'dependency',
    metadata: METADATA,
     inject: 'head',
    filename: './sub-app.html',
    chunks: ['polyfills','sub-main']
  }),

下一个任务是分开创建适用于开发环境的两个子应用的端点。

The next task was to create separate endpoints for both sub apps for dev environment.

devServer: {
      port: METADATA.port,
      host: METADATA.host,
      historyApiFallback: true,
      watchOptions: {
        aggregateTimeout: 300,
        poll: 1000
      },
      proxy: {
   "/sub-app": {
    target: "http://localhost:3009",
    bypass: function(req, res, proxyOptions) {
        return "/index2.html";
    }
  }
}
    },

现在,当我构建项目时,会生成两个不同的HTML文件。每个都有自己的javascript捆绑依赖项和公共资产。它们也可以部署在不同的服务器上。

Now when I build the project two different HTML files are generated. Each with their own javascript bundle dependencies and common assets. They can be deployed on a different server as well.

我能够通过大量的试验和错误来完成我的POC。我的建议是在角度上方迈出一步。了解webpack如何部署您当前的项目。如果您可以配置它以满足您的目的。

I was able to finish my POC with lots of trial and error. My suggestion will be to look a step above angular. See how webpack is deploying your current project. And If you can configure it to serve your purpose.

这篇关于动态加载外部webpack捆绑的ngModule作为路由处理程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆