diff --git a/libs/mf-runtime/package.json b/libs/mf-runtime/package.json index 411f86b0..764fd205 100644 --- a/libs/mf-runtime/package.json +++ b/libs/mf-runtime/package.json @@ -1,10 +1,10 @@ { "name": "@angular-architects/module-federation-runtime", "license": "MIT", - "version": "17.0.7", + "version": "18.0.3", "peerDependencies": { - "@angular/common": ">=17.0.0", - "@angular/core": ">=17.0.0" + "@angular/common": ">=18.0.0", + "@angular/core": ">=18.0.0" }, "dependencies": { "tslib": "^2.0.0" diff --git a/libs/mf-tools/package.json b/libs/mf-tools/package.json index 64e99559..0e8333d2 100644 --- a/libs/mf-tools/package.json +++ b/libs/mf-tools/package.json @@ -1,6 +1,6 @@ { "name": "@angular-architects/module-federation-tools", - "version": "17.0.7", + "version": "18.0.3", "license": "MIT", "peerDependencies": {}, "dependencies": { diff --git a/libs/mf/collection.json b/libs/mf/collection.json index eec8f3c5..a1c8a9aa 100644 --- a/libs/mf/collection.json +++ b/libs/mf/collection.json @@ -27,6 +27,11 @@ "factory": "./src/schematics/boot-async/schematic", "schema": "./src/schematics/boot-async/schema.json", "description": "Enables or disables async bootstrapping" + }, + "remove": { + "factory": "./src/schematics/remove/schematic", + "schema": "./src/schematics/remove/schema.json", + "description": "Removes Module Federation" } } } diff --git a/libs/mf/package.json b/libs/mf/package.json index 163e13cd..5f8754bf 100644 --- a/libs/mf/package.json +++ b/libs/mf/package.json @@ -1,6 +1,6 @@ { "name": "@angular-architects/module-federation", - "version": "17.0.7", + "version": "18.0.3", "license": "MIT", "repository": { "type": "GitHub", @@ -17,7 +17,7 @@ "schematics": "./collection.json", "builders": "./builders.json", "dependencies": { - "@angular-architects/module-federation-runtime": "17.0.7", + "@angular-architects/module-federation-runtime": "18.0.3", "word-wrap": "^1.2.3", "callsite": "^1.0.0", "node-fetch": "^2.6.7", diff --git a/libs/mf/post-build.js b/libs/mf/post-build.js index b3442f35..3d58feea 100644 --- a/libs/mf/post-build.js +++ b/libs/mf/post-build.js @@ -1,21 +1,34 @@ const fs = require('fs'); const path = require('path'); -const index = fs.readFileSync(path.join(__dirname, 'src/index.ts'), { - encoding: 'utf-8', -}); -fs.writeFileSync( - path.join(__dirname, '../../dist/libs/mf/src/index.js'), - index -); -const nguniversal = fs.readFileSync( - path.join(__dirname, 'src/nguniversal.ts'), +// const index = fs.readFileSync(path.join(__dirname, 'src/index.ts'), { +// encoding: 'utf-8', +// }); +// fs.writeFileSync( +// path.join(__dirname, '../../dist/libs/mf/src/index.js'), +// index +// ); + +// const nguniversal = fs.readFileSync( +// path.join(__dirname, 'src/nguniversal.ts'), +// { +// encoding: 'utf-8', +// } +// ); +// fs.writeFileSync( +// path.join(__dirname, '../../dist/libs/mf/src/nguniversal.js'), +// nguniversal +// ); + + +const webpack2 = fs.readFileSync( + path.join(__dirname, 'webpack.ts'), { encoding: 'utf-8', } ); fs.writeFileSync( - path.join(__dirname, '../../dist/libs/mf/src/nguniversal.js'), - nguniversal -); + path.join(__dirname, '../../dist/libs/mf/webpack.js'), + 'module.exports = require("./src/webpack.js");' +); \ No newline at end of file diff --git a/libs/mf/project.json b/libs/mf/project.json index 6d2bcabe..b29539ef 100644 --- a/libs/mf/project.json +++ b/libs/mf/project.json @@ -61,10 +61,6 @@ "post-build": { "dependsOn": ["build"], "executor": "nx:run-commands", - "outputs": [ - "{options.outputPath}/src/index.js", - "{options.outputPath}/src/nguniversal.ts" - ], "options": { "cwd": "libs/mf", "command": "node post-build.js" diff --git a/libs/mf/src/schematics/mf/schematic.ts b/libs/mf/src/schematics/mf/schematic.ts index 3c45d05e..e15ec5c6 100644 --- a/libs/mf/src/schematics/mf/schematic.ts +++ b/libs/mf/src/schematics/mf/schematic.ts @@ -86,7 +86,7 @@ function makeMainAsync(main: string, options: MfSchematicSchema): Rule { if (options.type === 'dynamic-host') { newMainContent = `import { initFederation } from '@angular-architects/module-federation'; -initFederation('/assets/mf.manifest.json') +initFederation('mf.manifest.json') .catch(err => console.error(err)) .then(_ => import('./bootstrap')) .catch(err => console.error(err)); @@ -227,13 +227,31 @@ export default function config(options: MfSchematicSchema): Rule { .join(projectRoot, 'webpack.prod.config.js') .replace(/\\/g, '/'); const manifestPath = path - .join(projectRoot, 'src/assets/mf.manifest.json') + .join(projectRoot, 'public/mf.manifest.json') .replace(/\\/g, '/'); const buildConfig = projectConfig?.architect?.build; const isApplicationBuilder = buildConfig?.builder === '@angular-devkit/build-angular:application'; + if (isApplicationBuilder) { + console.warn(`\nWARNING: This package uses the tradtional webpack-based Module Federation implementation and not the fast new esbuild-based ApplicationBuilder.`) + console.warn(`\nFor new projects, consider Native Federation as an alternative: https://shorturl.at/0ZQ0j`) + console.warn(`\nHowever, if you want to add a new host or remote to an existing Module Federation-based system, this package is what you are looking for.`) + console.warn(`\nDo you want to proceeed: [y] Yes [n] No \n`) + + for (; ;) { + const key = await readKey(); + if (key === 'Y' || key === 'y') { + break; + } + if (key === 'N' || key === 'n') { + process.exit(0); + } + } + + } + if (buildConfig?.options?.browser) { buildConfig.options.main = buildConfig.options.browser; delete buildConfig.options.browser; @@ -241,6 +259,10 @@ export default function config(options: MfSchematicSchema): Rule { delete buildConfig.options.prerender; } + if (buildConfig?.options?.assets) { + updateAssets(); + } + const port = parseInt(options.port) || 4200; const main = projectConfig.architect.build.options.main; @@ -376,11 +398,11 @@ export default function config(options: MfSchematicSchema): Rule { const dep = getPackageJsonDependency(tree, 'ngx-build-plus'); - if (!dep || !semver.satisfies(dep.version, '>=17.0.0')) { + if (!dep || !semver.satisfies(dep.version, '>=18.0.0')) { addPackageJsonDependency(tree, { name: 'ngx-build-plus', type: NodeDependencyType.Dev, - version: '^17.0.0', + version: '^18.0.0', overwrite: true, }); @@ -392,9 +414,32 @@ export default function config(options: MfSchematicSchema): Rule { makeMainAsync(main, options), adjustSSR(projectSourceRoot, ssrMappings), ]); + + function updateAssets() { + for (const asset of buildConfig.options.assets) { + if (typeof asset === 'object' && typeof asset.output === 'undefined') { + asset.output = '.'; + } + } + } }; } +async function readKey() { + return await new Promise((r) => { + process.stdin.setRawMode(true); + process.stdin.resume(); + process.stdin.setEncoding('utf8'); + + process.stdin.on('data', (key) => { + process.stdin.setRawMode(false); + process.stdin.pause(); + r(key); + }); + + }); +} + function updateTsConfig(tree, tsConfigName: string) { const tsConfig = json5.parse(tree.read(tsConfigName).toString('utf-8')); const target = tsConfig.compilerOptions.target as string; @@ -499,3 +544,25 @@ export function generateSsrMappings( return remotes; } + +function downgradeToWebpack(build: any) { + + if (!build.options) { + return; + } + + if (build.options.browser) { + build.options.main = build.options.browser; + delete build.options.browser; + } + + if (!build.options.assets) { + return; + } + + for (const asset of build.options.assets) { + if (typeof asset === 'object' && typeof asset.output === 'undefined') { + asset.output = '.'; + } + } +} \ No newline at end of file diff --git a/libs/mf/src/schematics/remove/schema.d.ts b/libs/mf/src/schematics/remove/schema.d.ts new file mode 100644 index 00000000..a4960465 --- /dev/null +++ b/libs/mf/src/schematics/remove/schema.d.ts @@ -0,0 +1,3 @@ +export interface RemoveSchema { + project: string; +} diff --git a/libs/mf/src/schematics/remove/schema.json b/libs/mf/src/schematics/remove/schema.json new file mode 100644 index 00000000..5857dfcc --- /dev/null +++ b/libs/mf/src/schematics/remove/schema.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/schema", + "$id": "mf", + "title": "", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "The project to add module federation", + "x-prompt": "Project name (press enter for default project)" + } + } +} diff --git a/libs/mf/src/schematics/remove/schematic.ts b/libs/mf/src/schematics/remove/schematic.ts new file mode 100644 index 00000000..6bdb239e --- /dev/null +++ b/libs/mf/src/schematics/remove/schematic.ts @@ -0,0 +1,98 @@ +import path = require('path'); +import { noop } from 'rxjs'; +import { getWorkspaceFileName } from '../mf/schematic'; +import { Rule, Tree } from '@angular-devkit/schematics'; +import { RemoveSchema } from './schema'; + +export default function remove(options: RemoveSchema): Rule { + return async function (tree: Tree) { + const workspaceFileName = getWorkspaceFileName(tree); + const workspace = JSON.parse(tree.read(workspaceFileName).toString('utf8')); + const normalized = normalize(options, workspace); + + removeBootstrap(normalized, tree); + + updateBuildConfig(normalized); + updateServeConfig(normalized); + + tree.overwrite(workspaceFileName, JSON.stringify(workspace, null, 2)); + + return noop(); + }; +} + +function updateBuildConfig(normalized: { projectConfig: any; projectName: string; }) { + const build = normalized.projectConfig.architect.build; + build.builder = '@angular-devkit/build-angular:browser'; + delete build.options.extraWebpackConfig; + const buildProd = build.configurations.production; + delete buildProd.extraWebpackConfig; +} + +function updateServeConfig(normalized: { projectConfig: any; projectName: string; }) { + const serve = normalized.projectConfig.architect.serve; + serve.builder = '@angular-devkit/build-angular:dev-server'; + delete serve.options.extraWebpackConfig; + + const serveProd = serve.configurations.production; + delete serveProd.extraWebpackConfig; + + const prodTarget = serveProd.browserTarget; + if (prodTarget) { + delete serveProd.browserTarget; + serveProd.buildTarget = prodTarget; + } + + const serveDev = serve.configurations.development; + const devTarget = serveDev.browserTarget; + + if (devTarget) { + delete serveDev.browserTarget; + serveDev.buildTarget = devTarget; + } + +} + +function normalize(options: RemoveSchema, workspace: any) { + + if (!options.project) { + options.project = workspace.defaultProject; + } + + if (!options.project && Object.keys(workspace.projects).length > 0) { + options.project = Object.keys(workspace.projects)[0]; + } + + if (!options.project) { + throw new Error( + `No default project found. Please specifiy a project name!` + ); + } + + const projectName = options.project; + const projectConfig = workspace.projects[projectName]; + + if (!projectConfig?.architect?.build?.options?.main) { + throw new Error( + `architect.build.options.main not found for project ` + projectName + ); + } + return { projectConfig, projectName }; +} + +function removeBootstrap(normalized, tree) { + const currentMain = normalized.projectConfig.architect.build.options.main; + + const mainPath = path + .join(path.dirname(currentMain), 'main.ts') + .replace(/\\/g, '/'); + + const bootstrapPath = path + .join(path.dirname(currentMain), 'bootstrap.ts') + .replace(/\\/g, '/'); + + const content = tree.readText(bootstrapPath); + tree.overwrite(mainPath, content); + tree.delete(bootstrapPath); +} +