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

Rfc/issue 570 context and theme packs #669

Merged
merged 14 commits into from
Aug 6, 2021
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
20 changes: 17 additions & 3 deletions packages/cli/src/lifecycles/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ const defaultConfig = {
meta: [],
plugins: greenwoodPlugins,
markdown: { plugins: [], settings: {} },
prerender: true
prerender: true,
pagesDirectory: 'pages',
templatesDirectory: 'templates'
};

module.exports = readAndMergeConfig = async() => {
Expand All @@ -50,7 +52,7 @@ module.exports = readAndMergeConfig = async() => {

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

// workspace validation
if (workspace) {
Expand Down Expand Up @@ -101,7 +103,7 @@ module.exports = readAndMergeConfig = async() => {
}

if (plugins && plugins.length > 0) {
const types = ['resource', 'rollup', 'server'];
const types = ['context', 'resource', 'rollup', 'server'];

plugins.forEach(plugin => {
if (!plugin.type || types.indexOf(plugin.type) < 0) {
Expand Down Expand Up @@ -165,6 +167,18 @@ module.exports = readAndMergeConfig = async() => {
if (prerender === undefined && mode === 'spa') {
customConfig.prerender = false;
}

if (pagesDirectory && typeof pagesDirectory === 'string') {
customConfig.pagesDirectory = pagesDirectory;
} else if (pagesDirectory) {
reject(`Error: provided pagesDirectory "${pagesDirectory}" is not supported. Please make sure to pass something like 'docs/'`);
}

if (templatesDirectory && typeof templatesDirectory === 'string') {
customConfig.templatesDirectory = templatesDirectory;
} else if (templatesDirectory) {
reject(`Error: provided templatesDirectory "${templatesDirectory}" is not supported. Please make sure to pass something like 'layouts/'`);
}
}

resolve({ ...defaultConfig, ...customConfig });
Expand Down
5 changes: 2 additions & 3 deletions packages/cli/src/lifecycles/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ const dataDir = path.join(__dirname, '../data');
module.exports = initContexts = async({ config }) => {

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

try {
const projectDirectory = process.cwd();
const userWorkspace = path.join(config.workspace);
const pagesDir = path.join(userWorkspace, 'pages/');
const userTemplatesDir = path.join(userWorkspace, 'templates/');
const pagesDir = path.join(userWorkspace, `${config.pagesDirectory}/`);
const userTemplatesDir = path.join(userWorkspace, `${config.templatesDirectory}/`);

const context = {
dataDir,
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/plugins/resource/plugin-node-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,9 @@ class NodeModulesResource extends ResourceInterface {
const isAbsoluteNodeModulesFile = fs.existsSync(path.join(projectDirectory, url));
const nodeModulesUrl = isAbsoluteNodeModulesFile
? path.join(projectDirectory, url)
: path.join(projectDirectory, this.resolveRelativeUrl(projectDirectory, url));
: this.resolveRelativeUrl(projectDirectory, url)
? path.join(projectDirectory, this.resolveRelativeUrl(projectDirectory, url))
: url;

return Promise.resolve(nodeModulesUrl);
}
Expand Down
74 changes: 49 additions & 25 deletions packages/cli/src/plugins/resource/plugin-standard-html.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,37 @@ const remarkRehype = require('remark-rehype');
const { ResourceInterface } = require('../../lib/resource-interface');
const unified = require('unified');

// general refactoring
const getPageTemplate = (barePath, workspace, template) => {
const templatesDir = path.join(workspace, 'templates');
const pageIsHtmlPath = `${barePath.substring(0, barePath.lastIndexOf(`${path.sep}index`))}.html`;
function getCustomPageTemplates(contextPlugins, templateName) {
return contextPlugins
.map(plugin => plugin.templates)
.flat()
.filter((templateDir) => {
return templateName && fs.existsSync(path.join(templateDir, `${templateName}.html`));
});
}

if (template && fs.existsSync(`${templatesDir}/${template}.html`)) {
// use a predefined template, usually from markdown frontmatter
contents = fs.readFileSync(`${templatesDir}/${template}.html`, 'utf-8');
const getPageTemplate = (barePath, templatesDir, template, contextPlugins = []) => {
const pageIsHtmlPath = `${barePath.substring(0, barePath.lastIndexOf(`${path.sep}index`))}.html`;
const customPluginDefaultPageTemplates = getCustomPageTemplates(contextPlugins, 'page');
const customPluginPageTemplates = getCustomPageTemplates(contextPlugins, template);

if (template && customPluginPageTemplates.length > 0 || fs.existsSync(`${templatesDir}/${template}.html`)) {
// use a custom template, usually from markdown frontmatter
contents = customPluginPageTemplates.length > 0
? fs.readFileSync(`${customPluginPageTemplates[0]}/${template}.html`, 'utf-8')
: fs.readFileSync(`${templatesDir}/${template}.html`, 'utf-8');
} else if (fs.existsSync(`${barePath}.html`) || fs.existsSync(pageIsHtmlPath)) {
// if the page is already HTML, use that as the template
const indexPath = fs.existsSync(pageIsHtmlPath)
? pageIsHtmlPath
: `${barePath}.html`;

contents = fs.readFileSync(indexPath, 'utf-8');
} else if (fs.existsSync(`${templatesDir}/page.html`)) {
// else look for default page template
contents = fs.readFileSync(`${templatesDir}/page.html`, 'utf-8');
} else if (customPluginDefaultPageTemplates.length > 0 || fs.existsSync(`${templatesDir}/page.html`)) {
// else look for default page template from the user
contents = customPluginDefaultPageTemplates.length > 0
? fs.readFileSync(`${customPluginDefaultPageTemplates[0]}/page.html`, 'utf-8')
: fs.readFileSync(`${templatesDir}/page.html`, 'utf-8');
} else {
// fallback to using Greenwood's stock page template
contents = fs.readFileSync(path.join(__dirname, '../../templates/page.html'), 'utf-8');
Expand All @@ -42,15 +55,19 @@ const getPageTemplate = (barePath, workspace, template) => {
return contents;
};

const getAppTemplate = (contents, userWorkspace, customImports = []) => {
const getAppTemplate = (contents, templatesDir, customImports = [], contextPlugins) => {
function sliceTemplate(template, pos, needle, replacer) {
return template.slice(0, pos) + template.slice(pos).replace(needle, replacer);
}

const userAppTemplatePath = `${userWorkspace}/templates/app.html`;
let appTemplateContents = fs.existsSync(userAppTemplatePath)
? fs.readFileSync(userAppTemplatePath, 'utf-8')
: fs.readFileSync(path.join(__dirname, '../../templates/app.html'), 'utf-8');
const userAppTemplatePath = `${templatesDir}app.html`;
const customAppTemplates = getCustomPageTemplates(contextPlugins, 'app');

let appTemplateContents = customAppTemplates.length > 0
? fs.readFileSync(`${customAppTemplates[0]}/app.html`, 'utf-8')
: fs.existsSync(userAppTemplatePath)
? fs.readFileSync(userAppTemplatePath, 'utf-8')
: fs.readFileSync(path.join(__dirname, '../../templates/app.html'), 'utf-8');

const root = htmlparser.parse(contents, {
script: true,
Expand Down Expand Up @@ -246,11 +263,11 @@ class StandardHtmlResource extends ResourceInterface {
}

async shouldServe(url) {
const { userWorkspace } = this.compilation.context;
const { pagesDir } = this.compilation.context;
const relativeUrl = this.getRelativeUserworkspaceUrl(url);
const barePath = relativeUrl.endsWith(path.sep)
? `${userWorkspace}${path.sep}pages${relativeUrl}index`
: `${userWorkspace}${path.sep}pages${relativeUrl.replace('.html', '')}`;
? `${pagesDir}${relativeUrl}index`
: `${pagesDir}${relativeUrl.replace('.html', '')}`;

return Promise.resolve(this.extensions.indexOf(path.extname(relativeUrl)) >= 0 || path.extname(relativeUrl) === '') &&
(fs.existsSync(`${barePath}.html`) || barePath.substring(barePath.length - 5, barePath.length) === 'index')
Expand All @@ -261,7 +278,7 @@ class StandardHtmlResource extends ResourceInterface {
return new Promise(async (resolve, reject) => {
try {
const config = Object.assign({}, this.compilation.config);
const { userWorkspace, projectDirectory } = this.compilation.context;
const { pagesDir, userTemplatesDir, projectDirectory } = this.compilation.context;
const { mode } = this.compilation.config;
const normalizedUrl = this.getRelativeUserworkspaceUrl(url);
let customImports;
Expand All @@ -270,8 +287,8 @@ class StandardHtmlResource extends ResourceInterface {
let template = null;
let processedMarkdown = null;
const barePath = normalizedUrl.endsWith(path.sep)
? `${userWorkspace}${path.sep}pages${normalizedUrl}index`
: `${userWorkspace}${path.sep}pages${normalizedUrl.replace('.html', '')}`;
? `${pagesDir}${normalizedUrl}index`
: `${pagesDir}${normalizedUrl.replace('.html', '')}`;
const isMarkdownContent = fs.existsSync(`${barePath}.md`)
|| fs.existsSync(`${barePath.substring(0, barePath.lastIndexOf(`${path.sep}index`))}.md`)
|| fs.existsSync(`${barePath.replace(`${path.sep}index`, '.md')}`);
Expand All @@ -281,7 +298,7 @@ class StandardHtmlResource extends ResourceInterface {
? `${barePath}.md`
: fs.existsSync(`${barePath.substring(0, barePath.lastIndexOf(`${path.sep}index`))}.md`)
? `${barePath.substring(0, barePath.lastIndexOf(`${path.sep}index`))}.md`
: `${userWorkspace}${path.sep}pages${url.replace(`${path.sep}index.html`, '.md')}`;
: `${pagesDir}${url.replace(`${path.sep}index.html`, '.md')}`;
const markdownContents = await fs.promises.readFile(markdownPath, 'utf-8');
const rehypePlugins = [];
const remarkPlugins = [];
Expand Down Expand Up @@ -325,14 +342,21 @@ class StandardHtmlResource extends ResourceInterface {
}
}
}


// get context plugins
const contextPlugins = this.compilation.config.plugins.filter((plugin) => {
return plugin.type === 'context';
}).map((plugin) => {
return plugin.provider(this.compilation);
});

if (mode === 'spa') {
body = fs.readFileSync(this.compilation.graph[0].path, 'utf-8');
} else {
body = getPageTemplate(barePath, userWorkspace, template);
body = getPageTemplate(barePath, userTemplatesDir, template, contextPlugins);
}

body = getAppTemplate(body, userWorkspace, customImports);
body = getAppTemplate(body, userTemplatesDir, customImports, contextPlugins);
body = getUserScripts(body, projectDirectory);
body = getMetaContent(normalizedUrl.replace(/\\/g, '/'), config, body);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Use Case
* Run Greenwood build command with a bad value for pagesDirectory in a custom config.
*
* User Result
* Should throw an error.
*
* User Command
* greenwood build
*
* User Config
* {
* pagesDirectory: {}
* }
*
* User Workspace
* Greenwood default
*/
const expect = require('chai').expect;
const path = require('path');
const Runner = require('gallinago').Runner;

describe('Build Greenwood With: ', function() {
const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js');
const outputPath = __dirname;
let runner;

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

describe('Custom Configuration with a bad value for pagesDirectory', function() {
it('should throw an error that pagesDirectory must be a string', async function() {
try {
await runner.setup(outputPath);
await runner.runCommand(cliPath, 'build');
} catch (err) {
expect(err).to.contain('Error: provided pagesDirectory "[object Object]" is not supported. Please make sure to pass something like \'docs/\'');
}
});
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
pagesDirectory: {}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Use Case
* Run Greenwood build command with a bad value for templatesDirectory in a custom config.
*
* User Result
* Should throw an error.
*
* User Command
* greenwood build
*
* User Config
* {
* templatesDirectory: {}
* }
*
* User Workspace
* Greenwood default
*/
const expect = require('chai').expect;
const path = require('path');
const Runner = require('gallinago').Runner;

describe('Build Greenwood With: ', function() {
const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js');
const outputPath = __dirname;
let runner;

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

describe('Custom Configuration with a bad value for templatesDirectory', function() {
it('should throw an error that templatesDirectory must be a string', async function() {
try {
await runner.setup(outputPath);
await runner.runCommand(cliPath, 'build');
} catch (err) {
expect(err).to.contain('Error: provided templatesDirectory "[object Object]" is not supported. Please make sure to pass something like \'layouts/\'');
}
});
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
templatesDirectory: {}
};
Loading