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

Changes to API #4

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
35 changes: 35 additions & 0 deletions lib/datoMetaTags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const htmlTag = require('html-tag')
const { modifyNodes } = require('reshape-plugin-util')

module.exports = function datoMetaTags () {
return function datoMetaTagsPlugin (tree, ctx) {
if (ctx) {
Object.assign(ctx.runtime, { htmlTag })
}

return modifyNodes(
tree,
(node) => node.name === 'dato-meta-tags',
(node) => {
if (!(node.attrs && node.attrs.record)) {
throw new ctx.PluginError({
message: 'dato-meta-tags tag has no "record" attribute',
plugin: 'spike-datocms',
location: node.location
})
}

return {
type: 'code',
content: `(function() {
const record = locals.${node.attrs.record[0].content};
if (!record || !record.seoMetaTags) { return null; }
return record.seoMetaTags.map(tag => (
__runtime.htmlTag(tag.tagName, tag.attributes || {}, tag.content)
)).join('');
})()`
}
}
)
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Very clever way of making this happen! Curious if you had any other thoughts working with reshape and its API

Copy link
Author

@stefanoverna stefanoverna Jul 19, 2017

Choose a reason for hiding this comment

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

That was an hack actually 😛 I'm not using reshape to generate the HTML, resorting to some external helper. I don't think that's very advisable... it would be better to return some reshape AST nodes instead, so that they could then be further processed by other reshape plugins down the chain (ie. reshape/minify). I could not find a way to do that though... 🆘

187 changes: 33 additions & 154 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,20 @@ const fs = require('fs')
const reshape = require('reshape')
const loader = require('reshape-loader')
const {SiteClient} = require('datocms-client')
const DatoLoader = require('datocms-client/lib/local/Loader')
const bindAllClass = require('es6bindall')
const mkdirp = require('mkdirp')

module.exports = class SpikeDatoCMS {
constructor (opts) {
Object.assign(this, this.validate(opts))
this.client = new SiteClient(opts.token)
this.client = new DatoLoader(new SiteClient(opts.token))
bindAllClass(this, ['apply', 'run'])
}

apply (compiler) {
this.util = new SpikeUtil(compiler.options)
this.util.runAll(compiler, this.run)

// set cache to full path for use in emit + run functions
if (this.cache) {
this.cache = path.join(compiler.options.context, this.cache)
}

// TODO: this pulls the incorrect loader context
compiler.plugin('compilation', (compilation) => {
compilation.plugin('normal-module-loader', (loaderContext) => {
Expand All @@ -34,61 +29,21 @@ module.exports = class SpikeDatoCMS {
})

compiler.plugin('emit', (compilation, done) => {
if (this.json) {
writeJson(compilation, this.json, this.addDataTo.dato)
}
Copy link
Member

Choose a reason for hiding this comment

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

Also removing the writeJson feature?

Copy link
Author

Choose a reason for hiding this comment

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

that was just temporary, I can re-enable it, but I thought that was redundant, as you can use the the singlePages option to do the same. Well, not at the moment, because the writeTemplate is not using webpack, right?


// if cache is set and cache file doesn't exist; write it
if (this.cache && this.rewrite) {
writeCache(this.cache, this.addDataTo.dato)
Copy link
Member

Choose a reason for hiding this comment

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

why remove the cache feature?

Copy link
Author

Choose a reason for hiding this comment

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

Well, DatoCMS pushes some websocket notifications everytime the content changes:

const SiteChangeWatcher = require('datocms-client/dump/SiteChangeWatcher');
const watcher = new SiteChangeWatcher(site.id);
watcher.connect(function() { 
  console.log('Data changed!'); 
});

So I think we should definitely get rid of the cache option, and automatically cache between these notifications.

if (this.singlePages) {
W.map(this.singlePages(this.client.itemsRepo), (page) => {
return writeTemplate.call(this, compiler, compilation, page)
}).done(() => done(), done)
}

this.models.filter((m) => m.json).map((m) => {
return writeJson(compilation, m.json, this.addDataTo.dato[m.type])
})

W.map(this.models.filter((m) => m.template), (contentType) => {
return writeTemplate.call(this, compiler, compilation, contentType)
}).done(() => done(), done)
done()
})
}

run (compilation, done) {
return W.all([
fetchData.call(this),
W.reduce(this.models, (memo, model) => {
// format options
const options = {}
if (model.ids) { options['filter[ids]'] = model.ids }
if (model.query) { options['filter[query]'] = model.query }
if (model.offset) { options['page[offset]'] = model.offset }
if (model.limit) { options['page[limit]'] = model.limit }
const transformFn = model.transform ? model.transform : (x) => x

// fetch items and itemTypes
return W.all([
W(this.client.items.all(options)),
W(this.client.itemTypes.all())
])
// make sure linked entries are resolved
.then(resolveLinks)
// filter to the model type, if necessary
.then(([records, itemTypes]) => {
if (!model.type) return records
const t = itemTypes.find((it) => it.apiKey === model.type)
return records.filter((r) => r.itemType === t.id)
})
// transform if necessary
.then((res) => W.map(res, (entry) => transformFn(entry)))
// add resolved item to the response
.tap((res) => { memo[model.type || 'all'] = res })
.yield(memo)
}, {})
]).done(([site, models]) => {
// add to the locals
Object.assign(this.addDataTo, { dato: Object.assign(models, { _meta: site }) })
return this.client.load().then(() => {
Object.assign(this.addDataTo, { dato: this.client.itemsRepo })
done()
}, done)
})
}

/**
Expand All @@ -99,24 +54,7 @@ module.exports = class SpikeDatoCMS {
const schema = Joi.object().keys({
token: Joi.string().required(),
addDataTo: Joi.object().required(),
json: Joi.string(),
cache: Joi.string(),
models: Joi.array().items(
Joi.object().keys({
type: Joi.string().default(Joi.ref('name')),
name: Joi.string(), // this is an alias for type
ids: Joi.array().single(),
query: Joi.string(),
offset: Joi.number(),
limit: Joi.number(),
transform: Joi.func(),
json: Joi.string(),
template: Joi.object().keys({
path: Joi.string(),
output: Joi.func()
})
})
)
singlePages: Joi.func()
})

const res = Joi.validate(opts, schema, {
Expand All @@ -125,92 +63,33 @@ module.exports = class SpikeDatoCMS {
object: { child: '!![spike-datocms constructor] option {{reason}}' }
}
})

if (res.error) { throw new Error(res.error) }
return res.value
}
}

function fetchData () {
// if cache is set
if (this.cache) {
// if cache is set and cache file doesn't exist; write it
if (!fs.existsSync(this.cache)) {
this.rewrite = true
} else { // if cache is set and cache file exists; parse it
return W.resolve(JSON.parse(fs.readFileSync(this.cache, 'utf8')))
}
} else { // if cache isn't set, hit the API
return this.client.site.find()
}
}

// TODO: use proxies so there can be no infinite loopz
function resolveLinks ([records, itemTypes]) {
// find all model ids
const recordIds = records.map((r) => r.id)
// scan all model values
records.map((r) => {
for (let k in r) {
if (k === 'id') continue
// check to see if it is a model id, which means it's a link
// if so, replace the id with the actual item
if (Array.isArray(r[k])) {
r[k] = r[k].map(resolveLink.bind(null, recordIds, records))
} else {
r[k] = resolveLink(recordIds, records, r[k])
}
}
})
return [records, itemTypes]
}

function resolveLink (ids, records, record) {
if (ids.indexOf(record) > -1) {
return records.find((r2) => r2.id === record)
} else {
return record
}
}

function writeJson (compilation, filename, data) {
const src = JSON.stringify(data, null, 2)
compilation.assets[filename] = {
source: () => src,
size: () => src.length
}
}

function writeCache (filename, data) {
const src = JSON.stringify(data, null, 2)
return mkdirp(path.dirname(filename), function () {
fs.writeFileSync(filename, src)
})
}

// TODO: get rid of this, put the templates through webpack
function writeTemplate (compiler, compilation, model) {
const data = this.addDataTo.dato[model.type]
const filePath = path.join(compiler.options.context, model.template.path)

return node.call(fs.readFile.bind(fs), filePath, 'utf8').then((template) => {
return W.map(data, (item) => {
const newLocals = Object.assign({}, this.addDataTo, { item })

const options = loader.parseOptions.call(this.loaderContext, this.util.getSpikeOptions().reshape, {})

// for any plugins that pull locals from the options
options.locals = newLocals
options.filename = filePath

return reshape(options)
.process(template)
.then((res) => {
const html = res.output(newLocals)
compilation.assets[model.template.output(item)] = {
source: () => html,
size: () => html.length
}
})
})
function writeTemplate (compiler, compilation, page) {
const filePath = path.join(compiler.options.context, page.template)

return node.call(fs.readFile.bind(fs), filePath, 'utf8')
.then((template) => {
const newLocals = Object.assign({}, this.addDataTo, page.locals)
const options = loader.parseOptions.call(this.loaderContext, this.util.getSpikeOptions().reshape, {})

// for any plugins that pull locals from the options
options.locals = newLocals
options.filename = filePath

return reshape(options)
.process(template)
.then((res) => {
const html = res.output(newLocals)
compilation.assets[page.output] = {
source: () => html,
size: () => html.length
}
})
})
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@
"dependencies": {
"datocms-client": "^0.3.25",
"es6bindall": "^0.0.9",
"html-tag": "^1.0.0",
"joi": "^10.6.0",
"mkdirp": "^0.5.1",
"reshape": "^0.4.1",
"reshape-loader": "^1.0.0",
"spike-util": "^1.2.0",
"reshape-plugin-util": "^0.2.1",
"spike-util": "^1.3.0",
"when": "^3.7.8"
}
}
Loading