feat(angular): add opinionated mfe webpack helpers (#9233)

* feat(angular): add mfe helpers for sharing workspace libraries

* feat(angular): add share packages helper

* chore(angular): add webpack dep
This commit is contained in:
Colum Ferry 2022-03-10 19:35:18 +00:00 committed by GitHub
parent 5d447c559a
commit bef8fb3629
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 230 additions and 2 deletions

View File

@ -15,7 +15,8 @@
"webpack-merge",
"ts-node",
"tsconfig-paths",
"semver"
"semver",
"webpack"
],
"keepLifecycleScripts": true
}

View File

@ -50,6 +50,7 @@
"webpack-merge": "5.7.3",
"ts-node": "~9.1.1",
"tsconfig-paths": "^3.9.0",
"semver": "7.3.4"
"semver": "7.3.4",
"webpack": "^5.58.1"
}
}

View File

@ -13,6 +13,11 @@ import { normalizeOptions } from './lib';
import type { Schema } from './schema';
export function webpackServer(schema: Schema, context: BuilderContext) {
process.env.NX_TSCONFIG_PATH = joinPathFragments(
context.workspaceRoot,
'tsconfig.base.json'
);
const options = normalizeOptions(schema);
const workspaceConfig = new Workspaces(
context.workspaceRoot

View File

@ -0,0 +1,125 @@
jest.mock('fs');
jest.mock('@nrwl/workspace');
import * as fs from 'fs';
import * as workspace from '@nrwl/workspace';
import { sharePackages, shareWorkspaceLibraries } from './mfe-webpack';
describe('MFE Webpack Utils', () => {
afterEach(() => jest.clearAllMocks());
describe('ShareWorkspaceLibraries', () => {
it('should error when the tsconfig file does not exist', () => {
// ARRANGE
(fs.existsSync as jest.Mock).mockReturnValue(false);
// ACT
try {
shareWorkspaceLibraries(['@myorg/shared']);
} catch (error) {
// ASSERT
expect(error.message).toEqual(
'NX MFE: TsConfig Path for workspace libraries does not exist! (undefined)'
);
}
});
it('should create an object with correct setup', () => {
// ARRANGE
(fs.existsSync as jest.Mock).mockReturnValue(true);
(workspace.readTsConfig as jest.Mock).mockReturnValue({
options: {
paths: {
'@myorg/shared': ['/libs/shared/src/index.ts'],
},
},
});
// ACT
const sharedLibraries = shareWorkspaceLibraries(['@myorg/shared']);
// ASSERT
expect(sharedLibraries.getAliases()).toHaveProperty('@myorg/shared');
expect(sharedLibraries.getAliases()['@myorg/shared']).toContain(
'libs/shared/src/index.ts'
);
expect(sharedLibraries.getLibraries()).toEqual({
'@myorg/shared': {
eager: undefined,
requiredVersion: false,
},
});
});
it('should create an object with empty setup when tsconfig does not contain the shared lib', () => {
// ARRANGE
(fs.existsSync as jest.Mock).mockReturnValue(true);
(workspace.readTsConfig as jest.Mock).mockReturnValue({
options: {
paths: {},
},
});
// ACT
const sharedLibraries = shareWorkspaceLibraries(['@myorg/shared']);
// ASSERT
expect(sharedLibraries.getAliases()).toEqual({});
expect(sharedLibraries.getLibraries()).toEqual({});
});
});
describe('SharePackages', () => {
it('should throw when it cannot find root package.json', () => {
// ARRANGE
(fs.existsSync as jest.Mock).mockReturnValue(false);
// ACT
try {
sharePackages(['@angular/core']);
} catch (error) {
// ASSERT
expect(error.message).toEqual(
'NX MFE: Could not find root package.json to determine dependency versions.'
);
}
});
it('should correctly map the shared packages to objects', () => {
// ARRANGE
(fs.existsSync as jest.Mock).mockReturnValue(true);
(fs.readFileSync as jest.Mock).mockReturnValue(
JSON.stringify({
dependencies: {
'@angular/core': '~13.2.0',
'@angular/common': '~13.2.0',
rxjs: '~7.4.0',
},
})
);
// ACT
const packages = sharePackages([
'@angular/core',
'@angular/common',
'rxjs',
]);
// ASSERT
expect(packages).toEqual({
'@angular/core': {
singleton: true,
strictVersion: true,
requiredVersion: '~13.2.0',
},
'@angular/common': {
singleton: true,
strictVersion: true,
requiredVersion: '~13.2.0',
},
rxjs: {
singleton: true,
strictVersion: true,
requiredVersion: '~7.4.0',
},
});
});
});
});

View File

@ -0,0 +1,96 @@
import { readTsConfig } from '@nrwl/workspace';
import { existsSync, readFileSync } from 'fs';
import { NormalModuleReplacementPlugin } from 'webpack';
import { appRootPath as rootPath } from '@nrwl/tao/src/utils/app-root';
import { normalizePath, joinPathFragments } from '@nrwl/devkit';
import { dirname } from 'path';
import { ParsedCommandLine } from 'typescript';
export function shareWorkspaceLibraries(
libraries: string[],
tsConfigPath = process.env.NX_TSCONFIG_PATH
) {
if (!existsSync(tsConfigPath)) {
throw new Error(
`NX MFE: TsConfig Path for workspace libraries does not exist! (${tsConfigPath})`
);
}
const tsConfig: ParsedCommandLine = readTsConfig(tsConfigPath);
const tsconfigPathAliases = tsConfig.options?.paths;
if (!tsconfigPathAliases) {
return {
getAliases: () => [],
getLibraries: () => [],
getReplacementPlugin: () =>
new NormalModuleReplacementPlugin(/./, () => {}),
};
}
const pathMappings: { name: string; path: string }[] = [];
for (const [key, paths] of Object.entries(tsconfigPathAliases)) {
if (libraries && libraries.includes(key)) {
const pathToLib = normalizePath(joinPathFragments(rootPath, paths[0]));
pathMappings.push({
name: key,
path: pathToLib,
});
}
}
return {
getAliases: () =>
pathMappings.reduce(
(aliases, library) => ({ ...aliases, [library.name]: library.path }),
{}
),
getLibraries: (eager?: boolean) =>
pathMappings.reduce(
(libraries, library) => ({
...libraries,
[library.name]: { requiredVersion: false, eager },
}),
{}
),
getReplacementPlugin: () =>
new NormalModuleReplacementPlugin(/./, (req) => {
if (!req.request.startsWith('.')) {
return;
}
const from = req.context;
const to = normalizePath(joinPathFragments(req.context, req.request));
for (const library of pathMappings) {
const libFolder = normalizePath(dirname(library.path));
if (!from.startsWith(libFolder) && to.startsWith(libFolder)) {
req.request = library.name;
}
}
}),
};
}
export function sharePackages(packages: string[]) {
const pkgJsonPath = joinPathFragments(rootPath, 'package.json');
if (!existsSync(pkgJsonPath)) {
throw new Error(
'NX MFE: Could not find root package.json to determine dependency versions.'
);
}
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
return packages.reduce(
(shared, pkgName) => ({
...shared,
[pkgName]: {
singleton: true,
strictVersion: true,
requiredVersion: pkgJson.dependencies[pkgName],
},
}),
{}
);
}