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

Adding dynamic, configurable, meta component, to page template #73

Merged
merged 22 commits into from
May 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bad3d07
fix: adding meta web component
hutchgrant Apr 22, 2019
ecfb61e
fix: adding meta component to default page template and scaffold
hutchgrant May 2, 2019
188b18b
fix: resolving lint issues
hutchgrant May 2, 2019
00a056a
Merge branch 'master' of github.com:ProjectEvergreen/greenwood into t…
hutchgrant May 8, 2019
883c295
fix: merge config meta with graph, updated meta component to prevent …
hutchgrant May 9, 2019
3e23368
fix: merge config meta with graph, updated meta component to prevent …
hutchgrant May 9, 2019
ca68b40
fix: remove leftovers from merge
hutchgrant May 9, 2019
ba67bad
fix: refactor
hutchgrant May 9, 2019
647cc83
fix: update meta summary comment
hutchgrant May 9, 2019
fe4a7b4
test: adding tests for title and meta
hutchgrant May 9, 2019
2afc21a
test: adding more tests for og:url and og:title
hutchgrant May 9, 2019
b772c9a
fix: small refactor
hutchgrant May 9, 2019
3361a74
fix: small refactor
hutchgrant May 9, 2019
3ab9cba
Merge branch 'master' of github.com:ProjectEvergreen/greenwood into t…
hutchgrant May 11, 2019
b10607d
test: adding meta config tests
hutchgrant May 11, 2019
3de7878
fix: cleanup test description and summary
hutchgrant May 11, 2019
0599e68
fix: re-enable meta teardown
hutchgrant May 11, 2019
b475ba5
test: seperating meta and title tests, modifying smoke-tests, adding …
hutchgrant May 15, 2019
7077836
test: cleanup
hutchgrant May 15, 2019
2c08054
Merge branch 'master' into task/fix-issue-5-meta
thescientist13 May 15, 2019
416a1d5
test: updating custom styled page-template test
hutchgrant May 15, 2019
81cbb33
test: add meta smoke-test
hutchgrant May 15, 2019
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
9 changes: 6 additions & 3 deletions packages/cli/config/webpack.config.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,22 @@ const mapUserWorkspaceDirectory = (userPath) => {
return new webpack.NormalModuleReplacementPlugin(
new RegExp(`${directory}`),
(resource) => {
resource.request = resource.request.replace(new RegExp(`\.\.\/${directory}`), userPath);

// workaround to ignore cli/templates default imports when rewriting
if (!new RegExp('\/cli\/templates').test(resource.request)) {
resource.request = resource.request.replace(new RegExp(`\.\.\/${directory}`), userPath);
}
// remove any additional nests, after replacement with absolute path of user workspace + directory
const additionalNestedPathIndex = resource.request.lastIndexOf('..');

if (additionalNestedPathIndex > -1) {
resource.request = resource.request.substring(additionalNestedPathIndex + 2, resource.request.length);
}
}
);
};

module.exports = (config, context) => {
module.exports = ({ config, context }) => {
// dynamically map all the user's workspace directories for resolution by webpack
// this essentially helps us keep watch over changes from the user, and greenwood's build pipeline
const mappedUserDirectoriesForWebpack = getUserWorkspaceDirectories(context.userWorkspace).map(mapUserWorkspaceDirectory);
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/config/webpack.config.develop.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const rebuild = async() => {
};

module.exports = ({ config, context, graph }) => {
const configWithContext = commonConfig(config, context, graph);
const configWithContext = commonConfig({ config, context, graph });
const { devServer, publicPath } = config;
const { host, port } = devServer;

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/config/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const webpackMerge = require('webpack-merge');
const commonConfig = require(path.join(__dirname, '..', './config/webpack.config.common.js'));

module.exports = ({ config, context, graph }) => {
const configWithContext = commonConfig(config, context, graph);
const configWithContext = commonConfig({ config, context, graph });

return webpackMerge(configWithContext, {

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/lib/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module.exports = generateCompilation = () => {

// generate a graph of all pages / components to build
console.log('Generating graph of workspace files...');
compilation.graph = await generateGraph(compilation);
compilation = await generateGraph(compilation);

// generate scaffolding
console.log('Scaffolding out project files...');
Expand Down
21 changes: 18 additions & 3 deletions packages/cli/lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@ let defaultConfig = {
port: 1984,
host: 'http://localhost'
},
publicPath: '/'
publicPath: '/',
title: 'Greenwood App',
meta: []
};

module.exports = readAndMergeConfig = async() => {
// eslint-disable-next-line complexity
return new Promise((resolve, reject) => {
try {
// deep clone of default config
let customConfig = JSON.parse(JSON.stringify(defaultConfig));

if (fs.existsSync(path.join(process.cwd(), 'greenwood.config.js'))) {
const userCfgFile = require(path.join(process.cwd(), 'greenwood.config.js'));
const { workspace, devServer, publicPath } = userCfgFile;

const { workspace, devServer, publicPath, title, meta } = userCfgFile;

// workspace validation
if (workspace) {
Expand All @@ -41,6 +45,13 @@ module.exports = readAndMergeConfig = async() => {
}
}

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

if (publicPath) {
if (typeof publicPath !== 'string') {
reject('Error: greenwood.config.js publicPath must be a string');
Expand All @@ -50,7 +61,10 @@ module.exports = readAndMergeConfig = async() => {
}
}

// devServer checks
if (meta && meta.length > 0) {
customConfig.meta = meta;
}

if (devServer && Object.keys(devServer).length > 0) {

if (devServer.host) {
Expand All @@ -72,6 +86,7 @@ module.exports = readAndMergeConfig = async() => {
// console.log(`custom port provided => ${customConfig.devServer.port}`);
}
}

}
}

Expand Down
25 changes: 17 additions & 8 deletions packages/cli/lib/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const fm = require('front-matter');
const path = require('path');
const util = require('util');

const createGraphFromPages = async (pagesDir) => {
const createGraphFromPages = async (pagesDir, config) => {
let pages = [];
const readdir = util.promisify(fs.readdir);
const readFile = util.promisify(fs.readFile);
Expand All @@ -26,7 +26,8 @@ const createGraphFromPages = async (pagesDir) => {
if (isMdFile && !stats.isDirectory()) {
const fileContents = await readFile(filePath, 'utf8');
const { attributes } = fm(fileContents);
let { label, template } = attributes;
let { label, template, title } = attributes;
let { meta } = config;
let mdFile = '';

// if template not set, use default
Expand Down Expand Up @@ -54,8 +55,8 @@ const createGraphFromPages = async (pagesDir) => {

// set route to the nested pages path and file name(without extension)
route = completeNestedPath + route;
mdFile = `./${completeNestedPath}${fileRoute}.md`;
relativeExpectedPath = `'../${completeNestedPath}/${fileName}/${fileName}.js'`;
mdFile = `.${completeNestedPath}${fileRoute}.md`;
relativeExpectedPath = `'..${completeNestedPath}/${fileName}/${fileName}.js'`;
} else {
mdFile = `.${fileRoute}.md`;
relativeExpectedPath = `'../${fileName}/${fileName}.js'`;
Expand All @@ -64,6 +65,11 @@ const createGraphFromPages = async (pagesDir) => {
// generate a random element name
label = label || generateLabelHash(filePath);

// set <title></title> element text, override with markdown title
title = title || config.title;

// TODO: Allow for other, per page, dynamic, meta data, merge meta array

/*
* Variable Definitions
*----------------------
Expand All @@ -75,10 +81,11 @@ const createGraphFromPages = async (pagesDir) => {
* fileName: file name without extension/path, so that it can be copied to scratch dir with same name
* relativeExpectedPath: relative import path for generated component within a list.js file to later be
* imported into app.js root component
* elementLabel: the element name for the generated md page e.g. <wc-md-hello-world></wc-md-hello-world>
* title: the head <title></title> text
* meta: og graph meta array of objects { property/name, content }
*/

pages.push({ mdFile, label, route, template, filePath, fileName, relativeExpectedPath });
pages.push({ mdFile, label, route, template, filePath, fileName, relativeExpectedPath, title, meta });
}
if (stats.isDirectory()) {
await walkDirectory(filePath);
Expand Down Expand Up @@ -116,9 +123,11 @@ module.exports = generateGraph = async (compilation) => {

return new Promise(async (resolve, reject) => {
try {
const graph = await createGraphFromPages(compilation.context.pagesDir);
const { context, config } = compilation;

compilation.graph = await createGraphFromPages(context.pagesDir, config);

resolve(graph);
resolve(compilation);
} catch (err) {
reject(err);
}
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/lib/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const path = require('path');
const defaultTemplatesDir = path.join(__dirname, '../templates/');
const scratchDir = path.join(process.cwd(), './.greenwood/');
const publicDir = path.join(process.cwd(), './public');
const metaComponent = path.join(__dirname, '..', 'templates', './components/meta');

module.exports = initContexts = async({ config }) => {

Expand Down Expand Up @@ -44,7 +45,8 @@ module.exports = initContexts = async({ config }) => {
? path.join(userTemplatesDir, notFoundPageTemplate)
: path.join(defaultTemplatesDir, notFoundPageTemplate),
indexPageTemplate,
notFoundPageTemplate
notFoundPageTemplate,
metaComponent
};

if (!fs.existsSync(scratchDir)) {
Expand Down
24 changes: 24 additions & 0 deletions packages/cli/lib/scaffold.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,37 @@ const writePageComponentsFromTemplate = async (compilation) => {
});
};

const loadPageMeta = async (file, result, { metaComponent }) => {
return new Promise((resolve, reject) => {
try {
const { title, meta, route } = file;
const metadata = {
title,
meta
};

metadata.meta.push({ property: 'og:title', content: title });
metadata.meta.push({ property: 'og:url', content: route });

result = result.replace(/METAIMPORT/, `import '${metaComponent}'`);
result = result.replace(/METADATA/, `const metadata = ${JSON.stringify(metadata)}`);
result = result.replace(/METAELEMENT/, '<eve-meta .attributes=\${metadata}></eve-meta>');

resolve(result);
} catch (err) {
reject(err);
}
});
};

return Promise.all(compilation.graph.map(file => {
const context = compilation.context;

return new Promise(async(resolve, reject) => {
try {
let result = await createPageComponent(file, context);

result = await loadPageMeta(file, result, context);
let relPageDir = file.filePath.substring(context.pagesDir.length, file.filePath.length);
const pathLastBackslash = relPageDir.lastIndexOf('/');

Expand Down
72 changes: 72 additions & 0 deletions packages/cli/templates/components/meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { html, LitElement } from 'lit-element';

/*
* Take an attributes object with an array of meta objects, add them to an element and replace/add the element to DOM
* {
* title: 'my title',
* meta: [
* { property: 'og:site', content: 'greenwood' },
* { name: 'twitter:site', content: '@PrjEvergreen ' }
* ]
* }
*/

class meta extends LitElement {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be a good candidate as a POC for our "first" (second) package in our (pending) monorepo in terms of coming up with an initial approach for how consumers can extend Greenwood. (markup anyway)

Maybe some sort of configuration where users can provide these placeholder (or Greenwood can provide standard ones, like META) and then we can have plugins for each of these "hooks".

I think it will be good to consider this to help avoid a giant configuration file. Even something like meta could become complex and warrant its own standalone package / API.

My guess is users will include / configure plugins like this and pass it into our configuration.

Just thinking out load, but seems like we could probably get a basic RFC drummed up from this idea for a post Sprint 2 enhancement (basically a "use the platform" version of react-helmet?. 🤔 💡

Copy link
Member Author

@hutchgrant hutchgrant May 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this as well, I was tempted to add this to my evergreen web component library That way, there would be no issue with importing it.


static get properties() {
return {
attributes: {
type: Object
}
};
}

firstUpdated() {
let header = document.head;
let meta;

if (this.attributes) {
this.attributes.meta.map(attr => {
meta = document.createElement('meta');

const metaPropertyOrName = Object.keys(attr)[0];
const metaPropValue = Object.values(attr)[0];
let metaContentVal = Object.values(attr)[1];

// insert origin domain into url
if (metaPropValue === 'og:url') {
metaContentVal = window.location.origin + metaContentVal;
}

meta.setAttribute(metaPropertyOrName, metaPropValue);
meta.setAttribute('content', metaContentVal);

const oldmeta = header.querySelector(`[${metaPropertyOrName}="${metaPropValue}"]`);

// rehydration
if (oldmeta) {
header.replaceChild(meta, oldmeta);
} else {
header.appendChild(meta);
}
});
let title = document.createElement('title');

title.innerText = this.attributes.title;
const oldTitle = document.head.querySelector('title');

header.replaceChild(title, oldTitle);
}

}

render() {
return html`
<div>

</div>
`;
}
}

customElements.define('eve-meta', meta);
3 changes: 3 additions & 0 deletions packages/cli/templates/page-template.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { html, LitElement } from 'lit-element';
MDIMPORT;
METAIMPORT;
METADATA;

class PageTemplate extends LitElement {
render() {
return html`
METAELEMENT
<div class='wrapper'>
<div class='page-template content'>
<entry></entry>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('Build Greenwood With: ', async function() {
before(async function() {
await setup.runGreenwoodCommand('build');
});
runSmokeTest(['public', 'index', 'not-found', 'hello'], LABEL);
runSmokeTest(['public', 'index', 'not-found', 'hello', 'meta'], LABEL);
});

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

describe('Build Greenwood With: ', () => {
let setup;

before(async () => {
setup = new TestBed();
setup.setupTestBed(__dirname);
});

describe('Custom Configuration with a bad value for Title', () => {
it('should throw an error that title must be a string', async () => {
try {
await setup.runGreenwoodCommand('build');
} catch (err) {
expect(err).to.contain('greenwood.config.js title must be a string');
}
});
});

after(function() {
setup.teardownTestBed();
});

});
3 changes: 3 additions & 0 deletions test/cli/cases/build.config.error-title/greenwood.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
title: {}
};
Loading