Skip to content

Commit 1bd02e2

Browse files
committed
feat(nf): add schematics for appbuilder migration
1 parent 9ad70ef commit 1bd02e2

File tree

6 files changed

+309
-0
lines changed

6 files changed

+309
-0
lines changed

libs/native-federation/collection.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
"factory": "./src/schematics/remove/schematic",
1818
"schema": "./src/schematics/remove/schema.json",
1919
"description": "Removes native federation"
20+
},
21+
"appbuilder": {
22+
"factory": "./src/schematics/appbuilder/schematic",
23+
"schema": "./src/schematics/appbuilder/schema.json",
24+
"description": "Migrates for using the appbuilder"
2025
}
2126
}
2227
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
```diff
2+
{
3+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
4+
"version": 1,
5+
"newProjectRoot": "projects",
6+
"projects": {
7+
"nf17x0a": {
8+
"projectType": "application",
9+
"schematics": {},
10+
"root": "",
11+
"sourceRoot": "src",
12+
"prefix": "app",
13+
"architect": {
14+
"build": {
15+
"builder": "@angular-architects/native-federation:build",
16+
"options": {},
17+
"configurations": {
18+
"production": {
19+
"target": "nf17x0a:esbuild:production"
20+
},
21+
"development": {
22+
"target": "nf17x0a:esbuild:development",
23+
"dev": true
24+
}
25+
},
26+
"defaultConfiguration": "production"
27+
},
28+
"serve": {
29+
"builder": "@angular-architects/native-federation:build",
30+
"options": {
31+
- "target": "nf17x0a:esbuild:development",
32+
+ "target": "nf17x0a:serve-original:development",
33+
"rebuildDelay": 0,
34+
- "dev": true,
35+
- "port": 4200
36+
+ "dev": true
37+
}
38+
},
39+
"extract-i18n": {
40+
"builder": "@angular-devkit/build-angular:extract-i18n",
41+
"options": {
42+
"buildTarget": "nf17x0a:build"
43+
}
44+
},
45+
"test": {
46+
"builder": "@angular-devkit/build-angular:karma",
47+
"options": {
48+
"polyfills": [
49+
"zone.js",
50+
"zone.js/testing"
51+
],
52+
"tsConfig": "tsconfig.spec.json",
53+
"assets": [
54+
"src/favicon.ico",
55+
"src/assets"
56+
],
57+
"styles": [
58+
"src/styles.css"
59+
],
60+
"scripts": []
61+
}
62+
},
63+
"esbuild": {
64+
- "builder": "@angular-devkit/build-angular:browser-esbuild",
65+
+ "builder": "@angular-devkit/build-angular:application",
66+
"options": {
67+
"outputPath": "dist/nf17x0a",
68+
"index": "src/index.html",
69+
"polyfills": [
70+
"zone.js",
71+
"es-module-shims"
72+
],
73+
"tsConfig": "tsconfig.app.json",
74+
"assets": [
75+
"src/favicon.ico",
76+
"src/assets"
77+
],
78+
"styles": [
79+
"src/styles.css"
80+
],
81+
"scripts": [],
82+
- "main": "src/main.ts"
83+
+ "browser": "src/main.ts"
84+
},
85+
"configurations": {
86+
"production": {
87+
"budgets": [
88+
{
89+
"type": "initial",
90+
"maximumWarning": "500kb",
91+
"maximumError": "1mb"
92+
},
93+
{
94+
"type": "anyComponentStyle",
95+
"maximumWarning": "2kb",
96+
"maximumError": "4kb"
97+
}
98+
],
99+
"outputHashing": "all"
100+
},
101+
"development": {
102+
"optimization": false,
103+
"extractLicenses": false,
104+
"sourceMap": true
105+
}
106+
},
107+
"defaultConfiguration": "production"
108+
},
109+
"serve-original": {
110+
"builder": "@angular-devkit/build-angular:dev-server",
111+
"configurations": {
112+
"production": {
113+
- "buildTarget": "nf17x0a:build:production"
114+
+ "buildTarget": "nf17x0a:esbuild:production"
115+
},
116+
"development": {
117+
- "buildTarget": "nf17x0a:build:development"
118+
+ "buildTarget": "nf17x0a:esbuild:development"
119+
}
120+
},
121+
"defaultConfiguration": "development"
122+
}
123+
}
124+
}
125+
}
126+
}
127+
128+
```

libs/native-federation/src/builders/build/builder.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,23 @@ export async function* runBuilder(
5555
context: BuilderContext
5656
): AsyncIterable<BuilderOutput> {
5757
let target = targetFromTargetString(nfOptions.target);
58+
59+
if (target.target === 'esbuild') {
60+
logger.error('UPDATE NEEDED');
61+
logger.error('')
62+
logger.error('Since version 17.1, Native Federation uses Angular\'s');
63+
logger.error('Application-Builder and its Dev-Server.');
64+
logger.error('Please update your project config, e.g. in angular.json');
65+
logger.error('');
66+
logger.error('This command performs the needed update for default configs:');
67+
logger.error('');
68+
logger.error('\tng g @angular-architects/native-federation:appbuilder');
69+
logger.error('');
70+
logger.error('You need to run it once per application to migrate');
71+
logger.error('Please find more information here: https://shorturl.at/gADJW');
72+
return;
73+
}
74+
5875
let _options = (await context.getTargetOptions(
5976
target
6077
)) as unknown as JsonObject & Schema;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface MfSchematicSchema {
2+
project: string;
3+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"$schema": "http://json-schema.org/schema",
3+
"$id": "mf",
4+
"title": "",
5+
"type": "object",
6+
"properties": {
7+
"project": {
8+
"type": "string",
9+
"description": "The project to add module federation",
10+
"$default": {
11+
"$source": "argv",
12+
"index": 0
13+
},
14+
"x-prompt": "Project name (press enter for default project)"
15+
}
16+
}
17+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { Rule, Tree, noop } from '@angular-devkit/schematics';
2+
3+
import { MfSchematicSchema } from './schema';
4+
5+
import * as path from 'path';
6+
7+
type NormalizedOptions = {
8+
polyfills: string;
9+
projectName: string;
10+
projectRoot: string;
11+
projectSourceRoot: string;
12+
manifestPath: string;
13+
projectConfig: any;
14+
main: string;
15+
};
16+
17+
export default function remove(options: MfSchematicSchema): Rule {
18+
return async function (tree /*, context*/) {
19+
const workspaceFileName = getWorkspaceFileName(tree);
20+
const workspace = JSON.parse(tree.read(workspaceFileName).toString('utf8'));
21+
22+
const normalized = normalizeOptions(options, workspace);
23+
24+
updateWorkspaceConfig(tree, normalized, workspace, workspaceFileName);
25+
};
26+
}
27+
28+
function updateWorkspaceConfig(
29+
tree: Tree,
30+
options: NormalizedOptions,
31+
workspace: any,
32+
workspaceFileName: string
33+
) {
34+
const { projectConfig } = options;
35+
36+
if (!projectConfig?.architect?.build || !projectConfig?.architect?.serve) {
37+
throw new Error(
38+
`The project doen't have a build or serve target in angular.json!`
39+
);
40+
}
41+
42+
if (projectConfig.architect.esbuild) {
43+
projectConfig.architect.esbuild.builder = '@angular-devkit/build-angular:application';
44+
projectConfig.architect.esbuild.options.browser = projectConfig.architect.esbuild.options.main;
45+
delete projectConfig.architect.esbuild.options.main;
46+
}
47+
48+
if (projectConfig.architect['serve-original']) {
49+
const target = projectConfig.architect['serve-original'];
50+
if (target.configurations?.production) {
51+
target.configurations.production.buildTarget = target.configurations.production.buildTarget.replace(':build:', ':esbuild:');
52+
}
53+
if (target.configurations?.development) {
54+
target.configurations.development.buildTarget = target.configurations.development.buildTarget.replace(':build:', ':esbuild:');
55+
}
56+
57+
}
58+
59+
if (projectConfig.architect.serve) {
60+
const target = projectConfig.architect.serve;
61+
target.options.target = target.options.target.replace(
62+
':esbuild:',
63+
':serve-original:'
64+
);
65+
delete target.options.port;
66+
}
67+
68+
tree.overwrite(workspaceFileName, JSON.stringify(workspace, null, '\t'));
69+
}
70+
71+
function normalizeOptions(
72+
options: MfSchematicSchema,
73+
workspace: any
74+
): NormalizedOptions {
75+
if (!options.project) {
76+
options.project = workspace.defaultProject;
77+
}
78+
79+
const projects = Object.keys(workspace.projects);
80+
81+
if (!options.project && projects.length === 0) {
82+
throw new Error(
83+
`No default project found. Please specifiy a project name!`
84+
);
85+
}
86+
87+
if (!options.project) {
88+
console.log(
89+
'Using first configured project as default project: ' + projects[0]
90+
);
91+
options.project = projects[0];
92+
}
93+
94+
const projectName = options.project;
95+
const projectConfig = workspace.projects[projectName];
96+
97+
if (!projectConfig) {
98+
throw new Error(`Project ${projectName} not found!`);
99+
}
100+
101+
const projectRoot: string = projectConfig.root?.replace(/\\/g, '/');
102+
const projectSourceRoot: string = projectConfig.sourceRoot?.replace(
103+
/\\/g,
104+
'/'
105+
);
106+
107+
const manifestPath = path
108+
.join(projectRoot, 'src/assets/federation.manifest.json')
109+
.replace(/\\/g, '/');
110+
111+
const main = projectConfig.architect.build.options.main;
112+
113+
if (!projectConfig.architect.build.options.polyfills) {
114+
projectConfig.architect.build.options.polyfills = [];
115+
}
116+
117+
const polyfills = projectConfig.architect.build.options.polyfills;
118+
return {
119+
polyfills,
120+
projectName,
121+
projectRoot,
122+
projectSourceRoot,
123+
manifestPath,
124+
projectConfig,
125+
main,
126+
};
127+
}
128+
129+
export function getWorkspaceFileName(tree: Tree): string {
130+
if (tree.exists('angular.json')) {
131+
return 'angular.json';
132+
}
133+
if (tree.exists('workspace.json')) {
134+
return 'workspace.json';
135+
}
136+
throw new Error(
137+
"angular.json or workspace.json expected! Did you call this in your project's root?"
138+
);
139+
}

0 commit comments

Comments
 (0)