Skip to content

Commit

Permalink
feat(angular): add opinionated mfe webpack helpers (#9233)
Browse files Browse the repository at this point in the history
* feat(angular): add mfe helpers for sharing workspace libraries

* feat(angular): add share packages helper

* chore(angular): add webpack dep
  • Loading branch information
Coly010 committed Mar 10, 2022
1 parent 5d447c5 commit bef8fb3
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 2 deletions.
3 changes: 2 additions & 1 deletion packages/angular/ng-package.json
Expand Up @@ -15,7 +15,8 @@
"webpack-merge",
"ts-node",
"tsconfig-paths",
"semver"
"semver",
"webpack"
],
"keepLifecycleScripts": true
}
3 changes: 2 additions & 1 deletion packages/angular/package.json
Expand Up @@ -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"
}
}
Expand Up @@ -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
Expand Down
125 changes: 125 additions & 0 deletions packages/angular/src/utils/mfe-webpack.spec.ts
@@ -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',
},
});
});
});
});
96 changes: 96 additions & 0 deletions packages/angular/src/utils/mfe-webpack.ts
@@ -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],
},
}),
{}
);
}

0 comments on commit bef8fb3

Please sign in to comment.