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

Dynamic Sitemap Copy Plugin (#1232) #1260

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
36 changes: 36 additions & 0 deletions packages/cli/src/plugins/copy/plugin-copy-sitemap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import fs from 'fs/promises';

async function writeSitemap(compilation) {
try {
const { scratchDir, projectDirectory } = compilation.context;
const adapterScratchUrl = new URL('./sitemap.xml', scratchDir);

// Check if module exists
const sitemapModule = await import(`${projectDirectory}/src/sitemap.xml.js`);
const sitemap = await sitemapModule.generateSitemap(compilation);

await fs.writeFile(adapterScratchUrl, sitemap);
console.info('Wrote sitemap to ./sitemap.xml');

return adapterScratchUrl;
} catch (error) {
console.error('Error in sitemapAdapter:', error);
}
}

const greenwoodPluginCopySitemap = [{
type: 'copy',
name: 'plugin-copy-sitemap',
provider: async (compilation) => {

const { outputDir } = compilation.context;
const sitemapScratchUrl = await writeSitemap(compilation);

return [{
from: sitemapScratchUrl,
to: new URL('./sitemap.xml', outputDir)
}];
}
}];

export { greenwoodPluginCopySitemap };
37 changes: 37 additions & 0 deletions packages/cli/src/plugins/resource/plugin-resource-sitemap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ResourceInterface } from '@greenwood/cli/src/lib/resource-interface.js';

class SitemapResource extends ResourceInterface {
constructor(compilation, options) {
super(compilation, options);
}

async shouldServe(url) {
return url.pathname.endsWith('sitemap.xml');
}

// eslint-disable-next-line no-unused-vars
async serve(url) {

const { projectDirectory } = this.compilation.context;

try {
const sitemapModule = await import(`${projectDirectory}/src/sitemap.xml.js`);
const sitemap = await sitemapModule.generateSitemap(this.compilation);
return new Response(sitemap, { headers: { 'Content-Type': 'text/xml' } });

} catch (error) {
console.error('Error loading module: ./sitemap.xml.js Does it exist?', error);
return new Response('<error>Sitemap oops.</error>', { headers: { 'Content-Type': 'text/xml' } });
}

}

}

const greenwoodPluginSitemap = {
type: 'resource',
name: 'plugin-resource-sitemap',
provider: (compilation, options) => new SitemapResource(compilation, options)
};

export { greenwoodPluginSitemap };
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Use Case
* Run Greenwood with the sitemap adapter plugin.
*
* User Result
* Should generate a static Greenwood build with a sitemap rendered.
*
* User Command
* greenwood build
*
* User Config
* import { greenwoodPluginAdapterSitemap } from '../../../src/index.js';
*
* export default {
* plugins: [
* greenwoodPluginAdapterSitemap()
* ]
* };
*
* User Workspace
* TBD
*/
import chai from 'chai';
import fs from 'fs/promises';
import path from 'path';
import { checkResourceExists } from '../../../../cli/src/lib/resource-utils.js';
import { getSetupFiles } from '../../../../../test/utils.js';
import { Runner } from 'gallinago';
import { fileURLToPath } from 'url';

const expect = chai.expect;

describe('Build Greenwood With: ', function() {
const LABEL = 'Sitemap Adapter plugin output';
const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js');
const outputPath = fileURLToPath(new URL('.', import.meta.url));
const publicDir = path.join(outputPath, 'public');

let runner;

before(function() {
this.context = {
publicDir: path.join(outputPath, 'public')
};
runner = new Runner();
});

describe(LABEL, function() {
before(function() {
runner.setup(outputPath, getSetupFiles(outputPath));
runner.runCommand(cliPath, 'build');
});

describe('sitemap.xml', function() {
it('should be present', async function() {
const sitemapPath = path.join(publicDir, 'sitemap.xml');

const itExists = await checkResourceExists(new URL(`file://${sitemapPath}`));
expect(itExists).to.be.equal(true);

});

it('should have the correct first element in the list', async function() {
const sitemapPath = path.join(publicDir, 'sitemap.xml');
const text = await fs.readFile(sitemapPath, 'utf8');

const regex = /<loc>(http:\/\/www\.example\.com\/about\/)<\/loc>/;
const match = text.match(regex);

expect(match[1]).to.equal('http://www.example.com/about/');
});

});

});

after(function() {
runner.stopCommand();
runner.teardown([
path.join(outputPath, '.greenwood')
]);
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
plugins: []
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# About Us

Lorem ipsum.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Home Page

Welcome!
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
async function generateSitemap(compilation) {
const urls = compilation.graph.map((page) => {
return ` <url>
<loc>http://www.example.com${page.route}</loc>
</url>`;
});
return `
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.join('\n')}
</urlset>
`;
}

export { generateSitemap };
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

import path from 'path';
import { Runner } from 'gallinago';
import { fileURLToPath } from 'url';

import chai from 'chai';
const expect = chai.expect;

describe('Develop Sitemap With: ', function() {

const LABEL = 'Sitemap Resource plugin output';

const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js');
const outputPath = fileURLToPath(new URL('.', import.meta.url));
const hostname = 'http://localhost';
const port = 1984;
let runner;

before(function() {
this.context = {
hostname: `${hostname}:${port}`
};
runner = new Runner();
});

describe(LABEL, function() {

before(async function() {
runner.setup(outputPath);

return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 5000);

runner.runCommand(cliPath, 'develop', { async: true });
});
});

describe('Sitemap.xml', function() {
let response = {};
let text;

before(async function() {
response = await fetch(`${hostname}:${port}/sitemap.xml`);
text = await response.text();
});

it('should return a 200', function() {
expect(response.status).to.equal(200);
});

it('should return the correct content type', function() {
expect(response.headers.get('content-type')).to.equal('text/xml');
});

it('should contain loc element', function() {
const regex = /<loc>(http:\/\/www\.example\.com\/about\/)<\/loc>/;
const match = text.match(regex);

expect(match[1]).to.equal('http://www.example.com/about/');

});
});
});

after(function() {
runner.stopCommand();
runner.teardown([
path.join(outputPath, '.greenwood')
]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

export default {
plugins: [
]
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# About Us

Lorem ipsum.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Home Page

Welcome!
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
async function generateSitemap(compilation) {
const urls = compilation.graph.map((page) => {
return ` <url>
<loc>http://www.example.com${page.route}</loc>
</url>`;
});

return `
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.join('\n')}
</urlset>
`;
}

export { generateSitemap };
Loading