首页 文章

动态加载外部webpack捆绑的ngModule作为路由处理程序

提问于
浏览
17

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

根路由器抱怨说

Error: No NgModule metadata found for 'TestsetModule'.

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

根应用程序通过路由加载子项:

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

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

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

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”注意全局导出 .

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配置 . 注意“外部”,它将角度视为全局 .

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'读取 .

{
  "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旨在实现这一目标 . 但我们的用例是让孩子和父母在运行之前彼此无知 .

1 回答

  • 3

    正如你所提到的那样

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

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

    在webpack配置中,我补充道

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

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

    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']
      }),
    

    下一个任务是为dev环境的两个子应用程序创建单独的 endpoints .

    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捆绑依赖项和公共资产 . 它们也可以部署在不同的服务器上 .

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

相关问题