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

0.23.0 release branch (SSR) #868

Merged
merged 9 commits into from
Feb 12, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .c8rc.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@

"checkCoverage": true,

"statements": 85,
"statements": 80,
"branches": 85,
"functions": 90,
"lines": 85,
"lines": 80,

"watermarks": {
"statements": [75, 85],
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/)

## Overview
Greenwood is a modern and performant static site generator supporting Web Component based development. For more information about how to get started, read our docs, or learn more about the project and how it works, please visit our [website](https://www.greenwoodjs.io/).
Greenwood is a framework focused on supporting modern web standards and development to help you build your next project. For more information about how to get started, read our docs, or learn more about the project and how it works, please visit our [website](https://www.greenwoodjs.io/).

Features:
- ⚡ [No bundle development](https://www.greenwoodjs.io/about/how-it-works/). Pages are built on the fly.
Expand All @@ -16,7 +16,7 @@ Features:
- 🚫 No JavaScript by default.
- 📖 Prerendering support for Web Components.
- ⚒️ Extensible via [plugins](https://www.greenwoodjs.io/plugins/).
- ⚙️ Supports [SSG, MPA, and SPA](https://www.greenwoodjs.io/docs/configuration/#mode). ([SSR support](https://github.com/ProjectEvergreen/greenwood/discussions/576) coming soon!)
- ⚙️ Supports [SSG, MPA, SPA, and SSR* (or a hybrid!) project types](https://www.greenwoodjs.io/docs/configuration/#mode).

> Greenwood is currently working towards a [1.0 release](https://github.com/ProjectEvergreen/greenwood/milestone/3) with our recent [`v0.10.0`](https://github.com/ProjectEvergreen/greenwood/releases/tag/v0.10.0) introducing some exciting new changes and concepts to the project. If you're interested in learning more about the web and web development (at any skill level!), or interested in checking out our high level roadmap and how Greenwood got where it is today, please see our [Open Beta + RFC Google doc](https://docs.google.com/document/d/1MwDkszKvq81QgIYa8utJgyUgSpLZQx9eKCWjIikvfHU/). We would love to have your help making Greenwood! ✌️

Expand Down Expand Up @@ -49,7 +49,7 @@ Then in your _package.json_, add the `type` field and `scripts` for the CLI, lik

- `greenwood build`: Generates a production build of your project
- `greenwood develop`: Starts a local development server for your project
- `greenwood serve`: Generates a production build of the project and serves it locally on a simple web server.
- `greenwood serve`: Generates a production build of your project and runs it on a NodeJS based web server

## Documentation
All of our documentation is on our [website](https://www.greenwoodjs.io/) (which itself is built by Greenwood!). See our website documentation to learn more about:
Expand All @@ -63,7 +63,7 @@ All of our documentation is on our [website](https://www.greenwoodjs.io/) (which
We would love your [contribution](.github/CONTRIBUTING.md) to Greenwood! Please check out our issue tracker for "good first issue" labels or feel to reach out to us on [Slack](https://join.slack.com/t/thegreenhouseio/shared_invite/enQtMzcyMzE2Mjk1MjgwLTU5YmM1MDJiMTg0ODk4MjA4NzUwNWFmZmMxNDY5MTcwM2I0MjYxN2VhOTEwNDU2YWQwOWQzZmY1YzY4MWRlOGI) in the room _"Greenwood"_ or on [Twitter](https://twitter.com/PrjEvergreen).

## Built With Greenwood
| Site | Repo | Project Details |
| Site | Repo | Project Details |
|---|---|---|
| [The Greenhouse I/O](https://www.thegreenhouse.io/) | [thegreenhouseio/www.thegreenhouse.io](https://github.com/thegreenhouseio/www.thegreenhouse.io) | Personal portfolio / blog website for @thescientist13 (Greenwood maintainer). |
| [Contributary](https://www.contributary.community/) | [ContributaryCommunity/www.contributary.community](https://github.com/ContributaryCommunity/www.contributary.community) | A website (SPA) for browsing open source projects that are looking for contributions. |
Expand Down
4 changes: 2 additions & 2 deletions greenwood.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import { greenwoodPluginPostCss } from '@greenwood/plugin-postcss';
import rollupPluginAnalyzer from 'rollup-plugin-analyzer';
import { fileURLToPath, URL } from 'url';

const META_DESCRIPTION = 'A modern and performant static site generator supporting Web Component based development';
const META_DESCRIPTION = 'A modern framework focused on web standards to help you build your next project.';
const FAVICON_HREF = '/favicon.ico';

export default {
workspace: fileURLToPath(new URL('./www', import.meta.url)),
mode: 'mpa',
optimization: 'inline',
staticRouter: true,
title: 'Greenwood',
meta: [
{ name: 'description', content: META_DESCRIPTION },
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.22.1",
"version": "0.23.0-alpha.1",
"packages": [
"packages/*",
"www"
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "greenwood",
"private": true,
"description": "A modern and performant static site generator supporting Web Component based development.",
"description": "A modern framework focused on web standards to help you build your next project.",
"repository": "https://github.com/ProjectEvergreen/greenwood",
"author": "Owen Buckley <owen@thegreenhouse.io>",
"license": "MIT",
Expand All @@ -27,6 +27,9 @@
"lint:css": "stylelint \"./www/**/*.js\", \"./www/**/*.css\"",
"lint": "ls-lint && yarn lint:js && yarn lint:ts && yarn lint:css"
},
"resolutions": {
"lit": "^2.1.1"
},
"devDependencies": {
"@ls-lint/ls-lint": "^1.10.0",
"@typescript-eslint/eslint-plugin": "^4.28.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@greenwood/cli",
"version": "0.22.1",
"version": "0.23.0-alpha.1",
"description": "Greenwood CLI.",
"type": "module",
"repository": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/cli",
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/build.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { bundleCompilation } from '../lifecycles/bundle.js';
import { copyAssets } from '../lifecycles/copy.js';
import { devServer } from '../lifecycles/serve.js';
import { getDevServer } from '../lifecycles/serve.js';
import fs from 'fs';
import { generateCompilation } from '../lifecycles/compile.js';
import { preRenderCompilation, staticRenderCompilation } from '../lifecycles/prerender.js';
Expand All @@ -23,7 +23,7 @@ const runProductionBuild = async () => {
if (prerender) {
await new Promise(async (resolve, reject) => {
try {
(await devServer(compilation)).listen(port, async () => {
(await getDevServer(compilation)).listen(port, async () => {
console.info(`Started local development server at localhost:${port}`);

const servers = [...compilation.config.plugins.filter((plugin) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/develop.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { generateCompilation } from '../lifecycles/compile.js';
import { ServerInterface } from '../lib/server-interface.js';
import { devServer } from '../lifecycles/serve.js';
import { getDevServer } from '../lifecycles/serve.js';

const runDevServer = async () => {

Expand All @@ -10,7 +10,7 @@ const runDevServer = async () => {
const compilation = await generateCompilation();
const { port } = compilation.config.devServer;

(await devServer(compilation)).listen(port, () => {
(await getDevServer(compilation)).listen(port, () => {

console.info(`Started local development server at localhost:${port}`);

Expand Down
12 changes: 7 additions & 5 deletions packages/cli/src/commands/serve.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { generateCompilation } from '../lifecycles/compile.js';
import { prodServer } from '../lifecycles/serve.js';
import { getStaticServer, getHybridServer } from '../lifecycles/serve.js';

const runProdServer = async () => {

return new Promise(async (resolve, reject) => {

try {
const compilation = await generateCompilation();
const port = 8080;

(await prodServer(compilation)).listen(port, () => {
console.info(`Started production test server at localhost:${port}`);
const port = compilation.config.port;
const hasRoutes = compilation.graph.find(page => page.isSSR);
const server = hasRoutes ? getHybridServer : getStaticServer;

(await server(compilation)).listen(port, () => {
console.info(`Started server at localhost:${port}`);
});
} catch (err) {
reject(err);
Expand Down
2 changes: 0 additions & 2 deletions packages/cli/src/lib/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ class BrowserRunner {
// Serialize page.
const content = await page.content();

// console.debug('content????', content);

await page.close();

return content;
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/lib/resource-interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ class ResourceInterface {
// ex: add a "banner" to all .js files with a timestamp of the build, or minifying files
// return true | false
// eslint-disable-next-line no-unused-vars
async shouldOptimize(url, body) {
async shouldOptimize(url, body, headers) {
return Promise.resolve(false);
}

// return the new body
// eslint-disable-next-line no-unused-vars
async optimize (url, body) {
async optimize (url, body, headers) {
return Promise.resolve(body);
}
}
Expand Down
29 changes: 29 additions & 0 deletions packages/cli/src/lib/ssr-route-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// https://github.com/nodejs/modules/issues/307#issuecomment-858729422
import { pathToFileURL } from 'url';
import { workerData, parentPort } from 'worker_threads';

async function executeRouteModule({ modulePath, compilation, route, label, id }) {
const { getTemplate = null, getBody = null, getFrontmatter = null } = await import(pathToFileURL(modulePath)).then(module => module);
const parsedCompilation = JSON.parse(compilation);
const data = {
template: null,
body: null,
frontmatter: null
};

if (getTemplate) {
data.template = await getTemplate(parsedCompilation, route);
}

if (getBody) {
data.body = await getBody(parsedCompilation, route);
}

if (getFrontmatter) {
data.frontmatter = await getFrontmatter(parsedCompilation, route, label, id);
}

parentPort.postMessage(data);
}

executeRouteModule(workerData);
3 changes: 2 additions & 1 deletion packages/cli/src/lifecycles/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ const bundleCompilation = async (compilation) => {

return new Promise(async (resolve, reject) => {
try {
// https://rollupjs.org/guide/en/#differences-to-the-javascript-api
compilation.graph = compilation.graph.filter(page => !page.isSSR);

// https://rollupjs.org/guide/en/#differences-to-the-javascript-api
if (compilation.graph.length > 0) {
const rollupConfigs = await getRollupConfig(compilation);
const bundle = await rollup(rollupConfigs[0]);
Expand Down
70 changes: 48 additions & 22 deletions packages/cli/src/lifecycles/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { fileURLToPath, pathToFileURL, URL } from 'url';
const greenwoodPluginsBasePath = fileURLToPath(new URL('../plugins', import.meta.url));

const greenwoodPlugins = (await Promise.all([
path.join(greenwoodPluginsBasePath, 'copy'),
path.join(greenwoodPluginsBasePath, 'copy'),
path.join(greenwoodPluginsBasePath, 'renderer'),
path.join(greenwoodPluginsBasePath, 'resource'),
path.join(greenwoodPluginsBasePath, 'server')
].map(async (pluginDirectory) => {
Expand All @@ -29,18 +30,18 @@ const greenwoodPlugins = (await Promise.all([
};
});

const modes = ['ssg', 'mpa', 'spa'];
const optimizations = ['default', 'none', 'static', 'inline'];
const pluginTypes = ['copy', 'context', 'resource', 'rollup', 'server', 'source'];
const pluginTypes = ['copy', 'context', 'resource', 'rollup', 'server', 'source', 'renderer'];
const defaultConfig = {
workspace: path.join(process.cwd(), 'src'),
devServer: {
hud: true,
port: 1984,
extensions: []
},
mode: modes[0],
port: 8080,
optimization: optimizations[0],
interpolateFrontmatter: false,
title: 'My App',
meta: [],
plugins: greenwoodPlugins,
Expand All @@ -56,10 +57,10 @@ const readAndMergeConfig = async() => {
try {
// deep clone of default config
let customConfig = Object.assign({}, defaultConfig);

if (fs.existsSync(path.join(process.cwd(), 'greenwood.config.js'))) {
const userCfgFile = (await import(pathToFileURL(path.join(process.cwd(), 'greenwood.config.js')))).default;
const { workspace, devServer, title, markdown, meta, mode, optimization, plugins, prerender, pagesDirectory, templatesDirectory } = userCfgFile;
const { workspace, devServer, title, markdown, meta, optimization, plugins, port, prerender, staticRouter, pagesDirectory, templatesDirectory, interpolateFrontmatter } = userCfgFile;

// workspace validation
if (workspace) {
Expand All @@ -82,7 +83,7 @@ const readAndMergeConfig = async() => {
'common issues to check might be: \n' +
'- typo in your workspace directory name, or in greenwood.config.js \n' +
'- if using relative paths, make sure your workspace is in the same cwd as _greenwood.config.js_ \n' +
'- consider using an absolute path, e.g. path.join(__dirname, \'my\', \'custom\', \'path\') // <__dirname>/my/custom/path/ ');
'- consider using an absolute path, e.g. new URL(\'/your/relative/path/\', import.meta.url)');
}
}

Expand All @@ -97,18 +98,19 @@ const readAndMergeConfig = async() => {
customConfig.meta = meta;
}

if (typeof mode === 'string' && modes.indexOf(mode.toLowerCase()) >= 0) {
customConfig.mode = mode;
} else if (mode) {
reject(`Error: provided mode "${mode}" is not supported. Please use one of: ${modes.join(', ')}.`);
}

if (typeof optimization === 'string' && optimizations.indexOf(optimization.toLowerCase()) >= 0) {
customConfig.optimization = optimization;
} else if (optimization) {
reject(`Error: provided optimization "${optimization}" is not supported. Please use one of: ${optimizations.join(', ')}.`);
}

if (interpolateFrontmatter) {
if (typeof interpolateFrontmatter !== 'boolean') {
reject('Error: greenwood.config.js interpolateFrontmatter must be a boolean');
}
customConfig.interpolateFrontmatter = interpolateFrontmatter;
}

if (plugins && plugins.length > 0) {
plugins.forEach(plugin => {
if (!plugin.type || pluginTypes.indexOf(plugin.type) < 0) {
Expand All @@ -128,6 +130,13 @@ const readAndMergeConfig = async() => {
}
});

// if user provides a custom renderer, replace ours with theirs
if (plugins.filter(plugin => plugin.type === 'renderer').length === 1) {
customConfig.plugins = customConfig.plugins.filter((plugin) => {
return plugin.type !== 'renderer';
});
}

customConfig.plugins = customConfig.plugins.concat(plugins);
}

Expand Down Expand Up @@ -168,18 +177,14 @@ const readAndMergeConfig = async() => {
customConfig.markdown.settings = markdown.settings ? markdown.settings : {};
}

if (prerender !== undefined) {
if (typeof prerender === 'boolean') {
customConfig.prerender = prerender;
if (port) {
// eslint-disable-next-line max-depth
if (!Number.isInteger(port)) {
reject(`Error: greenwood.config.js port must be an integer. Passed value was: ${port}`);
} else {
reject(`Error: greenwood.config.js prerender must be a boolean; true or false. Passed value was typeof: ${typeof prerender}`);
customConfig.port = port;
}
}

// SPA should _not_ prerender if user has specified prerender should be true
if (prerender === undefined && mode === 'spa') {
customConfig.prerender = false;
}

if (pagesDirectory && typeof pagesDirectory === 'string') {
customConfig.pagesDirectory = pagesDirectory;
Expand All @@ -192,6 +197,27 @@ const readAndMergeConfig = async() => {
} else if (templatesDirectory) {
reject(`Error: provided templatesDirectory "${templatesDirectory}" is not supported. Please make sure to pass something like 'layouts/'`);
}

if (prerender !== undefined) {
if (typeof prerender === 'boolean') {
customConfig.prerender = prerender;
} else {
reject(`Error: greenwood.config.js prerender must be a boolean; true or false. Passed value was typeof: ${typeof prerender}`);
}
}

// SPA should _not_ prerender unless if user has specified prerender should be true
if (prerender === undefined && fs.existsSync(path.join(customConfig.workspace, 'index.html'))) {
customConfig.prerender = false;
}

if (staticRouter !== undefined) {
if (typeof staticRouter === 'boolean') {
customConfig.staticRouter = staticRouter;
} else {
reject(`Error: greenwood.config.js staticRouter must be a boolean; true or false. Passed value was typeof: ${typeof staticRouter}`);
}
}
}

resolve({ ...defaultConfig, ...customConfig });
Expand Down
Loading