Skip to content

Commit

Permalink
refactor API routes and adapters for mapped API bundles
Browse files Browse the repository at this point in the history
  • Loading branch information
thescientist13 committed Feb 19, 2024
1 parent b0c4842 commit 59136f3
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 20 deletions.
49 changes: 48 additions & 1 deletion packages/cli/src/config/rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable complexity */
import fs from 'fs';
import path from 'path';
import { checkResourceExists, normalizePathnameForWindows } from '../lib/resource-utils.js';
Expand Down Expand Up @@ -141,7 +142,7 @@ function greenwoodSyncPageResourceBundlesPlugin(compilation) {

function getMetaImportPath(node) {
return node.arguments[0].value.split('/').join(path.sep)
.replace(/\\/g, '/') // handle Windows style paths
.replace(/\\/g, '/'); // handle Windows style paths
}

function isNewUrlImportMetaUrl(node) {
Expand Down Expand Up @@ -257,6 +258,28 @@ function greenwoodImportMetaUrl(compilation) {
const ref = this.emitFile(emitConfig);
const importRef = `import.meta.ROLLUP_FILE_URL_${ref}`;

// loop through all URL bundle chunks from APIs and SSR pages
// and map to their parent file, to pick back up in generateBundle when full hashes are known
if (id.indexOf(compilation.context.apisDir.pathname) === 0) {
for (const entry of compilation.manifest.apis.keys()) {
const apiRoute = compilation.manifest.apis.get(entry);

if (id.endsWith(apiRoute.path)) {
const assets = apiRoute.assets || [];

assets.push(assetUrl.url.pathname);

compilation.manifest.apis.set(entry, {
...apiRoute,
assets
});
}
}
} else {
// TODO figure out how to handle URL chunk from SSR pages

Check warning on line 279 in packages/cli/src/config/rollup.config.js

View workflow job for this annotation

GitHub Actions / build (18)

Unexpected 'todo' comment: 'TODO figure out how to handle URL chunk...'

Check warning on line 279 in packages/cli/src/config/rollup.config.js

View workflow job for this annotation

GitHub Actions / build (18)

Unexpected ' TODO' comment: 'TODO figure out how to handle URL chunk...'

Check warning on line 279 in packages/cli/src/config/rollup.config.js

View workflow job for this annotation

GitHub Actions / build (18)

Unexpected 'todo' comment: 'TODO figure out how to handle URL chunk...'

Check warning on line 279 in packages/cli/src/config/rollup.config.js

View workflow job for this annotation

GitHub Actions / build (18)

Unexpected ' TODO' comment: 'TODO figure out how to handle URL chunk...'
// https://github.com/ProjectEvergreen/greenwood/issues/1163
}

modifiedCode = code
.replace(`'${relativeAssetPath}'`, importRef)
.replace(`"${relativeAssetPath}"`, importRef);
Expand All @@ -266,6 +289,30 @@ function greenwoodImportMetaUrl(compilation) {
code: modifiedCode ? modifiedCode : code,
map: null
};
},

generateBundle(options, bundles) {
for (const bundle in bundles) {
const bundleExtension = bundle.split('.').pop();
const apiKey = `/api/${bundle.replace(`.${bundleExtension}`, '')}`;

if (compilation.manifest.apis.has(apiKey)) {
const apiManifestDetails = compilation.manifest.apis.get(apiKey);

for (const reference of bundles[bundle].referencedFiles) {
if (bundles[reference]) {
const assets = apiManifestDetails.assets;
const assetIdx = assets.indexOf(bundles[reference].facadeModuleId);

assets[assetIdx] = new URL(`./api/${reference}`, compilation.context.outputDir).href;
compilation.manifest.apis.set(apiKey, {
...apiManifestDetails,
assets
});
}
}
}
}
}
};
}
Expand Down
15 changes: 5 additions & 10 deletions packages/plugin-adapter-netlify/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,11 @@ async function netlifyAdapter(compilation) {
redirects += `${basePath}/api/* /.netlify/functions/api-:splat 200`;
}

for (const [key] of apiRoutes) {
for (const [key, value] of apiRoutes.entries()) {
const outputType = 'api';
const id = key.replace(`${basePath}/api/`, '');
const outputRoot = new URL(`./api/${id}/`, adapterOutputUrl);

const { assets = [] } = value;
await setupOutputDirectory(id, outputRoot, outputType);

await fs.cp(
Expand All @@ -141,15 +141,10 @@ async function netlifyAdapter(compilation) {
{ recursive: true }
);

// need this for URL referenced chunks
// TODO ideally we would map bundles to specific API routes instead of copying all files just in case
const ssrApiAssets = (await fs.readdir(new URL('./api/', outputDir)))
.filter(file => new RegExp(/^[\w][\w-]*\.[a-zA-Z0-9]{4,20}\.[\w]{2,4}$/).test(path.basename(file)));

for (const asset of ssrApiAssets) {
for (const asset of assets) {
await fs.cp(
new URL(`./${asset}`, new URL('./api/', outputDir)),
new URL(`./${asset}`, outputRoot),
new URL(asset),
new URL(`./${asset.split(path.sep).pop()}`, outputRoot),
{ recursive: true }
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,22 @@ describe('Build Greenwood With: ', function() {
expect(headers.get('content-type')).to.be.equal('application/json');
expect(JSON.parse(body).message).to.be.equal(`Hello ${param}!`);
});

it('should not have a shared asset for the card component', async () => {
const name = path.basename(apiFunctions[0]).replace('.zip', '');

await extract(apiFunctions[0], {
dir: path.join(normalizePathnameForWindows(netlifyFunctionsOutputUrl), name)
});

const assets = await glob.promise(path.join(normalizePathnameForWindows(netlifyFunctionsOutputUrl), `/${name}/*`));
const exists = assets.find((asset) => {
const name = asset.split(path.sep).pop();
return name.startsWith('card') && name.endsWith('.js');
});

expect(!!exists).to.equal(false);
});
});

describe('Fragments API Route adapter', function() {
Expand Down Expand Up @@ -158,6 +174,22 @@ describe('Build Greenwood With: ', function() {
expect(cardTags.length).to.be.equal(2);
expect(headers.get('content-type')).to.be.equal('text/html');
});

it('should have a shared asset for the card component', async () => {
const name = path.basename(apiFunctions[0]).replace('.zip', '');

await extract(apiFunctions[0], {
dir: path.join(normalizePathnameForWindows(netlifyFunctionsOutputUrl), name)
});

const assets = await glob.promise(path.join(normalizePathnameForWindows(netlifyFunctionsOutputUrl), `/${name}/*`));
const exists = assets.find((asset) => {
const name = asset.split(path.sep).pop();
return name.startsWith('card') && name.endsWith('.js');
});

expect(!!exists).to.equal(true);
});
});

describe('Submit JSON API Route adapter', function() {
Expand Down
14 changes: 5 additions & 9 deletions packages/plugin-adapter-vercel/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,11 @@ async function vercelAdapter(compilation) {
}
}

for (const [key] of apiRoutes) {
for (const [key, value] of apiRoutes.entries()) {
const outputType = 'api';
const id = key.replace(`${basePath}/api/`, '');
const outputRoot = new URL(`./${basePath}/api/${id}.func/`, adapterOutputUrl);
const { assets = [] } = value;

await setupFunctionBuildFolder(id, outputType, outputRoot);

Expand All @@ -121,15 +122,10 @@ async function vercelAdapter(compilation) {
{ recursive: true }
);

// need this for URL referenced chunks
// TODO ideally we would map bundles to specific API routes instead of copying all files just in case
const ssrApiAssets = (await fs.readdir(new URL('./api/', outputDir)))
.filter(file => new RegExp(/^[\w][\w-]*\.[a-zA-Z0-9]{4,20}\.[\w]{2,4}$/).test(path.basename(file)));

for (const asset of ssrApiAssets) {
for (const asset of assets) {
await fs.cp(
new URL(`./${asset}`, new URL('./api/', outputDir)),
new URL(`./${asset}`, outputRoot),
new URL(asset),
new URL(`./${asset.split(path.sep).pop()}`, outputRoot),
{ recursive: true }
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ describe('Build Greenwood With: ', function() {
expect(headers.get('content-type')).to.be.equal('application/json');
expect(JSON.parse(body).message).to.be.equal(`Hello ${param}!`);
});

it('should not have a shared asset for the card component', async () => {
const assets = await glob.promise(path.join(normalizePathnameForWindows(vercelFunctionsOutputUrl), '/api/greeting.func/*'));
const exists = assets.find((asset) => {
const name = asset.split(path.sep).pop();
return name.startsWith('card') && name.endsWith('.js');
});

expect(!!exists).to.equal(false);
});
});

describe('Fragments API Route adapter', function() {
Expand Down Expand Up @@ -183,6 +193,16 @@ describe('Build Greenwood With: ', function() {
expect(cardTags.length).to.be.equal(2);
expect(headers.get('content-type')).to.be.equal('text/html');
});

it('should have a shared asset for the card component', async () => {
const assets = await glob.promise(path.join(normalizePathnameForWindows(vercelFunctionsOutputUrl), '/api/fragment.func/*'));
const exists = assets.find((asset) => {
const name = asset.split(path.sep).pop();
return name.startsWith('card') && name.endsWith('.js');
});

expect(!!exists).to.equal(true);
});
});

describe('Submit JSON API Route adapter', function() {
Expand Down

0 comments on commit 59136f3

Please sign in to comment.