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

Feature/issue 878 SSR + custom resources #930

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
"lerna": "lerna",
"clean": "rimraf ./**/.greenwood/** && rimraf ./**/public/** && rimraf ./coverage",
"clean:deps": "rimraf **/node_modules/**",
"build": "cross-env __GWD_ROLLUP_MODE__=strict node . build",
"serve": "node . serve",
"develop": "node . develop",
"build": "cross-env __GWD_ROLLUP_MODE__=strict greenwood build",
"serve": "greenwood serve",
"develop": "greenwood develop",
"test": "cross-env BROWSERSLIST_IGNORE_OLD_DATA=true __GWD_ROLLUP_MODE__=strict c8 mocha",
"test:tdd": "yarn test --watch",
"lint:js": "eslint \"*.js\" \"./packages/**/**/*.js\" \"./test/*.js\" \"./www/**/**/*.js\"",
Expand Down
85 changes: 85 additions & 0 deletions packages/cli/src/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// https://dev.to/valeriavg/how-to-use-custom-files-as-modules-in-nodejs-51lp
console.debug('@@@@@@@@@@@ bootstrap!!!!!!');

import path from 'path';
import { readAndMergeConfig as initConfig } from './lifecycles/config.js';
import { URL, pathToFileURL, fileURLToPath } from 'url';

const baseURL = pathToFileURL(`${process.cwd()}/`).href;
const config = await initConfig();
const plugins = config.plugins.filter(plugin => plugin.type === 'resource' && !plugin.isGreenwoodDefaultPlugin).map(plugin => plugin.provider({
context: {
projectDirectory: process.cwd()
}
}));

function getCustomLoaderPlugins(url, body, headers) {
return plugins.filter(plugin => plugin.extensions.includes(path.extname(url)) && (plugin.shouldServe(url, body, headers) || plugin.shouldIntercept(url, body, headers)));
}

export function resolve(specifier, context, defaultResolve) {
const { parentURL = baseURL } = context;

// Node.js normally errors on unknown file extensions, so return a URL for
// specifiers ending in the specified file extensions.
if (getCustomLoaderPlugins(specifier).length > 0) {
return {
url: new URL(specifier, parentURL).href
};
}

return defaultResolve(specifier, context, defaultResolve);
}

export function getFormat(url, context, defaultGetFormat) {
// Now that we patched resolve to let new file types through, we need to
// tell Node.js what format such URLs should be interpreted as.
if (getCustomLoaderPlugins(url).length > 0) {
return {
format: 'module'
};
}
// Let Node.js handle all other URLs.
return defaultGetFormat(url, context, defaultGetFormat);
}

export async function transformSource(source, context, defaultTransformSource) {
const { url } = context;
const serverPlugins = getCustomLoaderPlugins(url, source);

if (serverPlugins.length) {
let contents = source.toString();

for (const plugin of serverPlugins) {
const headers = {
request: {
originalUrl: `${url}?type=${path.extname(url).replace('.', '')}`,
accept: ''
}
};
if (await plugin.shouldServe(url, headers)) {
contents = (await plugin.serve(url, headers)).body || contents;
}
}

for (const plugin of serverPlugins) {
const headers = {
request: {
originalUrl: `${url}?type=${path.extname(url).replace('.', '')}`,
accept: ''
}
};

if (await plugin.shouldIntercept(fileURLToPath(url), contents, headers)) {
contents = (await plugin.intercept(fileURLToPath(url), contents, headers)).body || contents;
}
}

return {
source: contents
};
}

// Let Node.js handle all other sources.
return Promise.resolve(defaultTransformSource(source, context, defaultTransformSource));
}
2 changes: 1 addition & 1 deletion packages/cli/src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env node
#!/usr/bin/env node --experimental-loader ./node_modules/@greenwood/cli/src/bootstrap.js

/* eslint-disable no-underscore-dangle */

Expand Down
10 changes: 10 additions & 0 deletions www/pages/artist.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
* {
color: blue;
}

h1 {
width: 50%;
margin: 0 auto;
text-align: center;
color: red;
}
83 changes: 83 additions & 0 deletions www/pages/artists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import fetch from 'node-fetch';
import css from './artist.css';
import json from '../package.json';

async function getTemplate(compilation, route) {
return `
<html>
<head>
<title>${route}</title>
<meta name="description" content="${route} (this was generated server side!!!)">

<script>
console.log(${JSON.stringify(compilation.graph.map(page => page.title).join(''))});
</script>

<style>
${css}
</style>
</head>
<body>
<h1>This heading was rendered server side!</h1>
<marquee>Data From package.json: ${json.description} @ v${json.version}</marquee>
<content-outlet></content-outlet>
</body>
</html>
`;
}

async function getBody(compilation) {
const artists = await fetch('http://www.analogstudios.net/api/artists').then(resp => resp.json());
const timestamp = new Date().getTime();
const artistsListItems = artists
.filter(artist => artist.isActive === '1')
.map((artist) => {
const { id, name, bio, imageUrl } = artist;

return `
<tr>
<td>${id}</td>
<td>${name}</td>
<td>${bio}</td>
<td><img src="${imageUrl}"/></td>
</tr>
`;
});

return `
<body>
<h1>Hello from the server rendered artists page! 👋</h1>
<table>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Genre</th>
</tr>
${artistsListItems.join('')}
</table>
<h6>Fetched at: ${timestamp}</h6>
<pre>
${JSON.stringify(compilation.graph.map(page => page.title).join(''))}
</pre>
</body>
`;
}

async function getFrontmatter() {
return {
menu: 'navigation',
index: 7,
data: {
author: 'Project Evergreen',
date: '01-01-2021',
prerender: true
}
};
}

export {
getTemplate,
getBody,
getFrontmatter
};