Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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],
},
}),
{}
);
}