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

查看:29
本文介绍了动态加载外部 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);

我也尝试使用 angular router 的字符串解析语法,而不是您看到的这种奇怪的全局事物,但我遇到了类似的问题.

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 配置.请注意将 angular 视为全局的外部".

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
  }
}

如果你还在阅读,太棒了.当两个包都是同一个 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 文件.它有自己的依赖项,共享相同的节点模块.两个主要模块都引导不同的应用程序组件.我们在没有 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天全站免登陆