Skip to content

Commit 46b861b

Browse files
committed
feat(nf): add angular linker
1 parent 27eb1be commit 46b861b

14 files changed

+155
-59
lines changed
17 Bytes
Binary file not shown.

libs/native-federation-core/src/lib/config/with-native-federation.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,12 @@ function normalizeSharedMappings(
7777
sharedMappings: config.sharedMappings,
7878
});
7979

80-
const result = paths.filter((p) => !isInSkipList(p.key, skip));
80+
const result = paths.filter((p) =>
81+
!isInSkipList(p.key, skip) && p.key.includes('*'));
82+
83+
if (paths.find(p => p.key.includes('*'))) {
84+
console.warn('Sharing mapped paths with wildcards (*) not supported');
85+
}
8186

8287
return result;
8388
}

libs/native-federation-core/src/lib/core/build-adapter.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface BuildAdapterOptions {
1313
mappedPaths: MappedPath[];
1414
packageName?: string;
1515
esm?: boolean;
16+
kind: 'shared-package' | 'shared-mapping' | 'exposed';
1617
}
1718

1819
export type BuildAdapter = (options: BuildAdapterOptions) => Promise<void>;

libs/native-federation-core/src/lib/core/bundle-exposed.ts

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export async function bundleExposed(
2626
external: externals,
2727
outfile: outFilePath,
2828
mappedPaths: config.sharedMappings,
29+
kind: 'exposed',
2930
});
3031

3132
const hash = hashFile(outFilePath);

libs/native-federation-core/src/lib/core/bundle-shared-mappings.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export async function bundleSharedMappings(
2727
external: externals,
2828
outfile: outFilePath,
2929
mappedPaths: [],
30+
kind: 'shared-mapping'
3031
});
3132

3233
const hash = hashFile(outFilePath);

libs/native-federation-core/src/lib/core/bundle-shared.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@ export async function bundleShared(
4646
mappedPaths: config.sharedMappings,
4747
packageName: pi.packageName,
4848
esm: pi.esm,
49+
kind: 'shared-package',
4950
});
5051
}
5152
catch(e) {
5253
console.error('Error bundling', pi.packageName);
54+
console.error(e);
5355
console.info(`If you don't need this package, skip it in your federation.config.js!`);
5456
continue;
5557
}
@@ -71,9 +73,17 @@ export async function bundleShared(
7173
fedOptions.outputPath,
7274
outFileName
7375
);
74-
fs.copyFileSync(cachedFile, fullOutputPath);
76+
77+
copyFileIfExists(cachedFile, fullOutputPath);
7578
copySrcMapIfExists(cachedFile, fullOutputPath);
7679
}
7780

7881
return result;
7982
}
83+
84+
function copyFileIfExists(cachedFile: string, fullOutputPath: string) {
85+
if (fs.existsSync(cachedFile)) {
86+
fs.copyFileSync(cachedFile, fullOutputPath);
87+
}
88+
}
89+

libs/native-federation-core/src/lib/core/default-skip-list.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const DEFAULT_SKIP_LIST: SkipList = [
1111
'@angular-architects/native-federation-runtime',
1212
'es-module-shims',
1313
'zone.js',
14-
'tslib',
14+
// 'tslib',
1515
/\/schematics(\/|$)/,
1616
(pkg) => pkg.startsWith('@angular/') && !!pkg.match(/\/testing(\/|$)/)
1717
];

libs/native-federation/package.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
"@rollup/plugin-replace": "^4.0.0",
2222
"rollup": "^2.79.0",
2323
"rollup-plugin-node-externals": "^4.1.1",
24-
"esbuild": "^0.15.5"
24+
"esbuild": "^0.15.5",
25+
"@babel/core": "^7.19.0",
26+
"@softarc/native-federation": "1.0.0",
27+
"@softarc/native-federation-runtime": "1.0.0",
28+
"@rollup/plugin-json": "^4.1.0"
29+
},
30+
"peerDependencies": {
31+
"@angular/compiler-cli": "*"
2532
}
2633
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export async function runBuilder(
3737
const config = await loadFederationConfig(fedOptions);
3838
const externals = getExternals(config);
3939

40-
options.externalDependencies = externals;
40+
options.externalDependencies = externals.filter(e => e !== 'tslib');
4141
const output = await build(config, options, context);
4242

4343
await buildForFederation(config, fedOptions, externals);
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,110 @@
1-
import { BuildAdapter } from '@softarc/native-federation/build';
1+
import { BuildAdapter, MappedPath } from '@softarc/native-federation/build';
22
import * as esbuild from 'esbuild';
33
import { createCompilerPlugin } from '@angular-devkit/build-angular/src/builders/browser-esbuild/compiler-plugin';
44
import { createSharedMappingsPlugin } from './shared-mappings-plugin';
5-
import { prepareNodePackage } from './prepare-node-package';
5+
import { prepareNodePackage as runRollup } from './prepare-node-package';
66

7-
const SKIP_PACKAGE_PREPARATION = ['@angular', '@ngrx', 'rxjs', 'zone.js'];
7+
import * as fs from 'fs';
8+
import * as path from 'path';
9+
10+
import { PluginItem, transformAsync } from '@babel/core';
11+
12+
// const SKIP_PACKAGE_PREPARATION = ['@angular', '@ngrx', 'rxjs', 'zone.js'];
813

914
export const AngularEsBuildAdapter: BuildAdapter = async (options) => {
10-
const { entryPoint, tsConfigPath, external, outfile, mappedPaths, packageName, esm } = options;
15+
const {
16+
entryPoint,
17+
tsConfigPath,
18+
external,
19+
outfile,
20+
mappedPaths,
21+
packageName,
22+
esm,
23+
kind,
24+
} = options;
25+
26+
// const pNameOrEmpty = packageName ?? '';
27+
28+
// const preparePackage = entryPoint.includes("node_modules")
29+
// && !(SKIP_PACKAGE_PREPARATION.find(p => pNameOrEmpty?.split('/')[0] === p)
30+
// || esm);
31+
32+
// const pkgName = preparePackage ? inferePkgName(entryPoint) : "";
33+
// const tmpFolder = `node_modules/.tmp/native-federation/${pkgName}`;
34+
35+
if (kind === 'shared-package') {
36+
await runRollup(entryPoint, external, outfile);
37+
} else {
38+
await runEsbuild(
39+
entryPoint,
40+
external,
41+
outfile,
42+
tsConfigPath,
43+
mappedPaths,
44+
// kind === 'shared-package' ? [] : null,
45+
// kind === 'shared-package' ? path.dirname(entryPoint) : undefined,
46+
);
47+
}
1148

12-
const pNameOrEmpty = packageName ?? '';
49+
if (kind === 'shared-package' && fs.existsSync(outfile)) {
50+
await link(outfile);
51+
}
52+
};
53+
54+
async function link(outfile: string) {
55+
console.log('linking shared package');
56+
const code = fs.readFileSync(outfile, 'utf-8');
57+
58+
try {
59+
const linkerEsm = await loadEsmModule<{ default: PluginItem; }>(
60+
'@angular/compiler-cli/linker/babel'
61+
);
62+
63+
const linker = linkerEsm.default;
1364

14-
const preparePackage = entryPoint.includes("node_modules")
15-
&& !(SKIP_PACKAGE_PREPARATION.find(p => pNameOrEmpty?.split('/')[0] === p)
16-
|| esm);
65+
const result = await transformAsync(code, {
66+
filename: outfile,
67+
// inputSourceMap: (useInputSourcemap ? undefined : false) as undefined,
68+
// sourceMaps: pluginOptions.sourcemap ? 'inline' : false,
69+
compact: false,
70+
configFile: false,
71+
babelrc: false,
72+
browserslistConfigFile: false,
73+
plugins: [linker],
74+
});
1775

18-
const pkgName = preparePackage ? inferePkgName(entryPoint) : "";
19-
const tmpFolder = `node_modules/.tmp/native-federation/${pkgName}`;
76+
fs.writeFileSync(outfile, result.code, 'utf-8');
77+
} catch (e) {
78+
console.error('error linking', e);
2079

21-
if (preparePackage) {
22-
await prepareNodePackage(entryPoint, external, tmpFolder);
80+
if (fs.existsSync(`${outfile}.error`)) {
81+
fs.unlinkSync(`${outfile}.error`);
82+
}
83+
fs.renameSync(outfile, `${outfile}.error`);
2384
}
85+
}
2486

87+
async function runEsbuild(
88+
entryPoint: string,
89+
external: string[],
90+
outfile: string,
91+
tsConfigPath: string,
92+
mappedPaths: MappedPath[],
93+
plugins: esbuild.Plugin[] | null = null,
94+
absWorkingDir: string | undefined = undefined
95+
) {
2596
await esbuild.build({
2697
entryPoints: [entryPoint],
2798
external,
99+
// absWorkingDir,
28100
outfile,
29101
bundle: true,
30102
sourcemap: true,
31103
minify: true,
32-
platform: 'node',
104+
platform: 'browser',
33105
format: 'esm',
34106
target: ['esnext'],
35-
plugins: [
107+
plugins: plugins || [
36108
createCompilerPlugin(
37109
{
38110
sourcemap: true,
@@ -51,11 +123,10 @@ export const AngularEsBuildAdapter: BuildAdapter = async (options) => {
51123
: []),
52124
],
53125
});
54-
};
55-
56-
function inferePkgName(entryPoint: string) {
57-
return entryPoint
58-
.replace(/.*?node_modules/g, "")
59-
.replace(/[^A-Za-z0-9.]/g, "_");
60126
}
61127

128+
export function loadEsmModule<T>(modulePath: string | URL): Promise<T> {
129+
return new Function('modulePath', `return import(modulePath);`)(
130+
modulePath
131+
) as Promise<T>;
132+
}
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,49 @@
1-
import { rollup } from "rollup";
2-
import resolve from "@rollup/plugin-node-resolve";
3-
import { externals } from "rollup-plugin-node-externals";
1+
import { rollup } from 'rollup';
2+
import resolve from '@rollup/plugin-node-resolve';
3+
import { externals } from 'rollup-plugin-node-externals';
44

55
// eslint-disable-next-line @typescript-eslint/no-var-requires
6-
const commonjs = require("@rollup/plugin-commonjs");
6+
const commonjs = require('@rollup/plugin-commonjs');
77

88
// eslint-disable-next-line @typescript-eslint/no-var-requires
9-
const replace = require("@rollup/plugin-replace");
9+
const replace = require('@rollup/plugin-replace');
1010

11-
export async function prepareNodePackage(entryPoint: string, external: string[], tmpFolder: string) {
12-
13-
console.log('Preparing package ...');
14-
11+
// eslint-disable-next-line @typescript-eslint/no-var-requires
12+
const json = require('@rollup/plugin-json');
13+
14+
15+
export async function prepareNodePackage(
16+
entryPoint: string,
17+
external: string[],
18+
outfile: string
19+
) {
20+
console.log('Converting package to esm ...');
21+
22+
try {
1523
const result = await rollup({
1624
input: entryPoint,
17-
25+
1826
plugins: [
27+
json(),
1928
commonjs(),
2029
externals({ include: external }),
2130
resolve(),
2231
replace({
2332
preventAssignment: true,
2433
values: {
25-
"process.env.NODE_ENV": '"development"',
34+
'process.env.NODE_ENV': '"development"',
2635
},
2736
}),
2837
],
2938
});
30-
39+
3140
await result.write({
32-
format: "esm",
33-
file: tmpFolder,
41+
format: 'esm',
42+
file: outfile,
3443
sourcemap: true,
35-
exports: "named",
44+
exports: 'named',
3645
});
37-
}
46+
} catch (e) {
47+
console.error('Error', e);
48+
}
49+
}

0 commit comments

Comments
 (0)