feat(cli): improve module federation, use @lowdefy/graphql-federated

This commit is contained in:
SamTolmay 2020-12-07 13:41:56 +02:00
parent 649db97233
commit 1139232308
9 changed files with 83 additions and 91 deletions

View File

@ -15,13 +15,18 @@
*/
import createContext from '../../utils/context';
import getBuildScript from '../../utils/getBuildScript';
import getFederatedModule from '../../utils/getFederatedModule';
async function build(options) {
const context = await createContext(options);
await getBuildScript(context);
const { default: buildScript } = await getFederatedModule({
module: 'build',
packageName: '@lowdefy/build',
version: context.version,
context,
});
context.print.info('Starting build.');
await context.buildScript({
await buildScript({
logger: context.print,
cacheDirectory: context.cacheDirectory,
configDirectory: context.baseDirectory,

View File

@ -19,7 +19,7 @@ import { spawnSync } from 'child_process';
import checkChildProcessError from '../../utils/checkChildProcessError';
import createContext from '../../utils/context';
import getBuildScript from '../../utils/getBuildScript';
import getFederatedModule from '../../utils/getFederatedModule';
import fetchNpmTarball from '../../utils/fetchNpmTarball';
async function buildNetlify(options) {
@ -33,7 +33,7 @@ async function buildNetlify(options) {
context.print.spin('Fetching Lowdefy Netlify server.');
await fetchNpmTarball({
name: '@lowdefy/server-netlify',
packageName: '@lowdefy/server-netlify',
version: context.version,
directory: netlifyDir,
});
@ -49,15 +49,21 @@ async function buildNetlify(options) {
message: 'Failed to npm install Netlify server.',
});
context.print.log('npm install successful.');
context.print.log(proccessOutput.stdout.toString('utf8'));
context.print.spin('Fetching Lowdefy build script.');
await getBuildScript(context);
const { default: buildScript } = await getFederatedModule({
module: 'build',
packageName: '@lowdefy/build',
version: context.version,
context,
});
context.print.log('Fetched Lowdefy build script.');
context.print.spin('Starting Lowdefy build.');
const outputDirectory = path.resolve(netlifyDir, './package/dist/functions/graphql/build');
await context.buildScript({
await buildScript({
logger: context.print,
cacheDirectory: context.cacheDirectory,
configDirectory: context.baseDirectory,

View File

@ -24,16 +24,26 @@ import { createGetSecretsFromEnv } from '@lowdefy/node-utils';
import BatchChanges from '../../utils/BatchChanges';
import createContext from '../../utils/context';
import getBuildScript from '../../utils/getBuildScript';
import getGraphql from './getGraphql';
import getFederatedModule from '../../utils/getFederatedModule';
import { outputDirectoryPath } from '../../utils/directories';
async function dev(options) {
// Setup
if (!options.port) options.port = 3000;
const context = await createContext(options);
await getBuildScript(context);
await getGraphql(context);
const { default: buildScript } = await getFederatedModule({
module: 'build',
packageName: '@lowdefy/build',
version: context.version,
context,
});
const { typeDefs, resolvers, createContext: createGqlContext } = await getFederatedModule({
module: 'graphql',
packageName: '@lowdefy/graphql-federated',
version: context.version,
context,
});
context.print.log('Starting Lowdefy development server.');
@ -43,7 +53,6 @@ async function dev(options) {
logger: console,
getSecrets: createGetSecretsFromEnv(),
};
const { typeDefs, resolvers, createContext: createGqlContext } = context.graphql;
const gqlContext = createGqlContext(config);
const server = new ApolloServer({ typeDefs, resolvers, context: gqlContext });
@ -63,7 +72,7 @@ async function dev(options) {
// File watcher
const fn = async () => {
context.print.log('Building configuration.');
await context.buildScript({
await buildScript({
logger: context.print,
cacheDirectory: context.cacheDirectory,
configDirectory: context.baseDirectory,

View File

@ -1,41 +0,0 @@
/*
Copyright 2020 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import fs from 'fs';
import path from 'path';
import fetchNpmTarball from '../../utils/fetchNpmTarball';
import loadModule from '../../utils/loadModule';
async function getGraphql(context) {
const cleanVersion = context.version.replace(/[-.]/g, '_');
const cachePath = path.resolve(context.cacheDirectory, `scripts/graphql_${cleanVersion}`);
if (!fs.existsSync(path.resolve(cachePath, 'package/dist/remoteEntry.js'))) {
context.print.spin(`Fetching @lowdefy/graphql@${context.version} to cache.`);
await fetchNpmTarball({
name: '@lowdefy/graphql',
version: context.version,
directory: cachePath,
});
context.print.log(`Fetched @lowdefy/build@${context.version} to cache.`);
}
context.graphql = await loadModule(
path.resolve(cachePath, 'package/dist/moduleFederation'),
'./graphql'
);
return context;
}
export default getGraphql;

View File

@ -18,24 +18,25 @@ import axios from 'axios';
import decompress from 'decompress';
import decompressTargz from 'decompress-targz';
async function fetchNpmTarball({ name, version, directory }) {
const registryUrl = `https://registry.npmjs.org/${name}`;
async function fetchNpmTarball({ packageName, version, directory }) {
const registryUrl = `https://registry.npmjs.org/${packageName}`;
let packageInfo;
try {
packageInfo = await axios.get(registryUrl);
} catch (error) {
if (error.response && error.response.status === 404) {
throw new Error(`Package "${name}" could not be found at ${registryUrl}.`);
console.log('404');
throw new Error(`Package "${packageName}" could not be found at ${registryUrl}.`);
}
throw error;
}
if (!packageInfo || !packageInfo.data) {
throw new Error(`Package "${name}" could not be found at ${registryUrl}.`);
throw new Error(`Package "${packageName}" could not be found at ${registryUrl}.`);
}
if (!packageInfo.data.versions[version]) {
throw new Error(`Invalid version. "${name}" does not have version "${version}".`);
throw new Error(`Invalid version. "${packageName}" does not have version "${version}".`);
}
let tarball;
@ -46,7 +47,7 @@ async function fetchNpmTarball({ name, version, directory }) {
} catch (error) {
if (error.response && error.response.status === 404) {
throw new Error(
`Package "${name}" tarball could not be found at ${packageInfo.data.versions[version].dist.tarball}.`
`Package "${packageName}" tarball could not be found at ${packageInfo.data.versions[version].dist.tarball}.`
);
}
throw error;
@ -54,7 +55,7 @@ async function fetchNpmTarball({ name, version, directory }) {
if (!tarball || !tarball.data) {
throw new Error(
`Package "${name}" tarball could not be found at ${packageInfo.data.versions[version].dist.tarball}.`
`Package "${packageName}" tarball could not be found at ${packageInfo.data.versions[version].dist.tarball}.`
);
}
await decompress(tarball.data, directory, {

View File

@ -89,43 +89,45 @@ jest.mock('axios', () => {
});
test('valid package and version', async () => {
await fetchNpmTarball({ name: 'valid-package', version: '1.0.0', directory });
await fetchNpmTarball({ packageName: 'valid-package', version: '1.0.0', directory });
expect(true).toBe(true);
});
test('version does not exist', async () => {
await expect(
fetchNpmTarball({ name: 'valid-package', version: 'invalid', directory })
fetchNpmTarball({ packageName: 'valid-package', version: 'invalid', directory })
).rejects.toThrow('Invalid version. "valid-package" does not have version "invalid"');
});
test('npm return a 404', async () => {
await expect(fetchNpmTarball({ name: '404', version: '1.0.0', directory })).rejects.toThrow(
'Package "404" could not be found at https://registry.npmjs.org/404.'
);
await expect(
fetchNpmTarball({ packageName: '404', version: '1.0.0', directory })
).rejects.toThrow('Package "404" could not be found at https://registry.npmjs.org/404.');
});
test('axios error', async () => {
await expect(
fetchNpmTarball({ name: 'axios-error', version: '1.0.0', directory })
fetchNpmTarball({ packageName: 'axios-error', version: '1.0.0', directory })
).rejects.toThrow('Axios error');
});
test('empty response', async () => {
await expect(fetchNpmTarball({ name: 'no-data', version: '1.0.0', directory })).rejects.toThrow(
'Package "no-data" could not be found at https://registry.npmjs.org/no-data.'
);
await expect(
fetchNpmTarball({ packageName: 'no-data', version: '1.0.0', directory })
).rejects.toThrow('Package "no-data" could not be found at https://registry.npmjs.org/no-data.');
});
test('undefined response', async () => {
await expect(fetchNpmTarball({ name: 'undefined', version: '1.0.0', directory })).rejects.toThrow(
await expect(
fetchNpmTarball({ packageName: 'undefined', version: '1.0.0', directory })
).rejects.toThrow(
'Package "undefined" could not be found at https://registry.npmjs.org/undefined.'
);
});
test('tarball 404', async () => {
await expect(
fetchNpmTarball({ name: 'valid-package', version: 'v404', directory })
fetchNpmTarball({ packageName: 'valid-package', version: 'v404', directory })
).rejects.toThrow(
'Package "valid-package" tarball could not be found at https://registry.npmjs.org/404.'
);
@ -133,13 +135,13 @@ test('tarball 404', async () => {
test('tarball axios error', async () => {
await expect(
fetchNpmTarball({ name: 'valid-package', version: 'error', directory })
fetchNpmTarball({ packageName: 'valid-package', version: 'error', directory })
).rejects.toThrow('Axios error');
});
test('tarball empty response', async () => {
await expect(
fetchNpmTarball({ name: 'valid-package', version: 'noData', directory })
fetchNpmTarball({ packageName: 'valid-package', version: 'noData', directory })
).rejects.toThrow(
'Package "valid-package" tarball could not be found at https://registry.npmjs.org/no-data.'
);
@ -147,7 +149,7 @@ test('tarball empty response', async () => {
test('tarball undefined response', async () => {
await expect(
fetchNpmTarball({ name: 'valid-package', version: 'undef', directory })
fetchNpmTarball({ packageName: 'valid-package', version: 'undef', directory })
).rejects.toThrow(
'Package "valid-package" tarball could not be found at https://registry.npmjs.org/undefined.'
);

View File

@ -19,21 +19,23 @@ import path from 'path';
import fetchNpmTarball from './fetchNpmTarball';
import loadModule from './loadModule';
async function getBuildScript(context) {
const cleanVersion = context.version.replace(/[-.]/g, '_');
const cachePath = path.resolve(context.cacheDirectory, `scripts/build_${cleanVersion}`);
async function getFederatedModule({ module, packageName, version, context }) {
const cleanVersion = version.replace(/[-.]/g, '_');
const cachePath = path.resolve(context.cacheDirectory, `scripts/${module}/${cleanVersion}`);
if (!fs.existsSync(path.resolve(cachePath, 'package/dist/remoteEntry.js'))) {
context.print.spin(`Fetching @lowdefy/build@${context.version} to cache.`);
context.print.spin(`Fetching ${packageName}@${version} to cache.`);
console.log('start fetch tarball');
await fetchNpmTarball({
name: '@lowdefy/build',
version: context.version,
packageName,
version,
directory: cachePath,
});
context.print.log(`Fetched @lowdefy/build@${context.version} to cache.`);
context.print.log(`Fetched ${packageName}@${version} to cache.`);
}
const buildScript = await loadModule(path.resolve(cachePath, 'package/dist'), './build');
context.buildScript = buildScript.default;
return context;
return loadModule({
directory: path.resolve(cachePath, 'package/dist'),
module: `./${module}`,
});
}
export default getBuildScript;
export default getFederatedModule;

View File

@ -18,7 +18,7 @@
import path from 'path';
async function loadModule(dir, moduleName, remoteEntry = 'remoteEntry.js') {
async function loadModule({ directory, module, remoteEntry = 'remoteEntry.js' }) {
const importRemote = async (remoteEntryFile) => {
if (__webpack_share_scopes__.default) {
await __webpack_init_sharing__('default');
@ -39,9 +39,9 @@ async function loadModule(dir, moduleName, remoteEntry = 'remoteEntry.js') {
});
};
const container = await importRemote(path.resolve(`${dir}/${remoteEntry}`));
const container = await importRemote(path.resolve(`${directory}/${remoteEntry}`));
return container.get(moduleName).then((factory) => factory());
return container.get(module).then((factory) => factory());
}
export default loadModule;

View File

@ -53,9 +53,17 @@ function createBasicPrint() {
};
}
// Memoise print so that error handler can get the same spinner object
let print;
function createPrint({ basic } = {}) {
if (basic) return createBasicPrint();
return createOraPrint();
if (print) return print;
if (basic) {
print = createBasicPrint();
return print;
}
print = createOraPrint();
return print;
}
export default createPrint;