Script to build examples

This commit is contained in:
Tim Schaub
2017-08-25 18:59:18 -06:00
committed by Tim Schaub
parent 7f47883c48
commit 93411a2b91
8 changed files with 282 additions and 8 deletions

View File

@@ -1,8 +1,8 @@
# Code examples
The `.html` files in this folder are built by applying the templates in the `config/examples/` folder. Examples have [YAML front-matter](http://www.metalsmith.io) headers with the following properties:
The `.html` files in this folder are built by applying the templates in the `templates` folder. Examples have [YAML front-matter](http://www.metalsmith.io) headers with the following properties:
* layout: The template from the `config/examples/` directory to use for this example
* layout: The template from the `templates` directory to use for this example
* title: The title of the example
* shortdesc: A short description for the example index
* docs: Documentation of the example. Can be markdown.

View File

@@ -52,7 +52,6 @@
<div class="span12">
<p id="shortdesc">{{ shortdesc }}</p>
<div id="docs">{{ md docs }}</div>
<div id="api-links">Related API documentation: {{{ js.apiHtml }}}</div>
</div>
</div>
@@ -105,7 +104,7 @@
var branchSearch = url.match(/\/([^\/]*)\/examples\//);
var cookieText = 'dismissed=-' + latestVersion + '-';
var dismissed = document.cookie.indexOf(cookieText) != -1;
if (!dismissed && /^v[0-9\.]*$/.test(branchSearch[1]) && '{{ olVersion }}' != latestVersion) {
if (branchSearch && !dismissed && /^v[0-9\.]*$/.test(branchSearch[1]) && '{{ olVersion }}' != latestVersion) {
var link = url.replace(branchSearch[0], '/latest/examples/');
fetch(link, {method: 'head'}).then(function(response) {
var a = document.getElementById('latest-link');

View File

@@ -0,0 +1,16 @@
{
"env": {
"node": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 2017
},
"rules": {
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}]
}
}

View File

@@ -0,0 +1,43 @@
const CopyPlugin = require('copy-webpack-plugin');
const ExampleBuilder = require('./example-builder');
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const src = path.join(__dirname, '..');
const examples = fs.readdirSync(src)
.filter(name => /^(?!index).*\.html$/.test(name))
.map(name => name.replace(/\.html$/, ''));
const entry = {};
examples.forEach(example => {
entry[example] = `./${example}.js`;
});
module.exports = {
context: src,
target: 'web',
entry: entry,
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks: 2
}),
new ExampleBuilder({
templates: path.join(__dirname, '..', 'templates'),
common: 'common'
}),
new CopyPlugin([
{from: '../css', to: 'css'},
{from: 'data', to: 'data'},
{from: 'resources', to: 'resources'},
{from: 'Jugl.js', to: 'Jugl.js'},
{from: 'index.html', to: 'index.html'}
])
],
output: {
filename: '[name].js',
path: path.join(__dirname, '..', '..', 'build', 'examples')
}
};

View File

@@ -0,0 +1,211 @@
const frontMatter = require('front-matter');
const fs = require('fs');
const handlebars = require('handlebars');
const marked = require('marked');
const path = require('path');
const pkg = require('../../package.json');
const promisify = require('util').promisify;
const readFile = promisify(fs.readFile);
const isCssRegEx = /\.css$/;
const isJsRegEx = /\.js(\?.*)?$/;
handlebars.registerHelper('md', str => new handlebars.SafeString(marked(str)));
handlebars.registerHelper('indent', (text, options) => {
if (!text) {
return text;
}
const count = options.hash.spaces || 2;
const spaces = new Array(count + 1).join(' ');
return text.split('\n').map(line => line ? spaces + line : '').join('\n');
});
/**
* Create an inverted index of keywords from examples. Property names are
* lowercased words. Property values are objects mapping example index to word
* count.
* @param {Array.<Object>} exampleData Array of example data objects.
* @return {Object} Word index.
*/
function createWordIndex(exampleData) {
const index = {};
const keys = ['shortdesc', 'title', 'tags'];
exampleData.forEach((data, i) => {
keys.forEach(key => {
let text = data[key];
if (Array.isArray(text)) {
text = text.join(' ');
}
let words = text ? text.split(/\W+/) : [];
words.forEach(word => {
if (word) {
word = word.toLowerCase();
let counts = index[word];
if (counts) {
if (index in counts) {
counts[i] += 1;
} else {
counts[i] = 1;
}
} else {
counts = {};
counts[i] = 1;
index[word] = counts;
}
}
});
});
});
return index;
}
/**
* A webpack plugin that builds the html files for our examples.
* @param {Object} config Plugin configuration. Requires a `templates` property
* with the path to templates and a `common` property with the name of the
* common chunk.
* @constructor
*/
function ExampleBuilder(config) {
this.templates = config.templates;
this.common = config.common;
}
/**
* Called by webpack.
* @param {Object} compiler The webpack compiler.
*/
ExampleBuilder.prototype.apply = function(compiler) {
compiler.plugin('emit', async (compilation, callback) => {
const chunks = compilation.getStats().toJson().chunks
.filter(chunk => chunk.names[0] !== this.common);
const exampleData = [];
const promises = chunks.map(async chunk => {
const [assets, data] = await this.render(compiler.context, chunk);
exampleData.push({
link: data.filename,
example: data.filename,
title: data.title,
shortdesc: data.shortdesc,
tags: data.tags
});
for (const file in assets) {
compilation.assets[file] = {
source: () => assets[file],
size: () => assets[file].length
};
}
});
try {
await Promise.all(promises);
} catch (err) {
callback(err);
return;
}
const info = {
examples: exampleData,
index: createWordIndex(exampleData)
};
const indexSource = `var info = ${JSON.stringify(info)}`;
compilation.assets['index.js'] = {
source: () => indexSource,
size: () => indexSource.length
};
callback();
});
};
ExampleBuilder.prototype.render = async function(dir, chunk) {
const name = chunk.names[0];
const assets = {};
const readOptions = {encoding: 'utf8'};
const htmlName = `${name}.html`;
const htmlPath = path.join(dir, htmlName);
const htmlSource = await readFile(htmlPath, readOptions);
const {attributes, body} = frontMatter(htmlSource);
const data = Object.assign(attributes, {contents: body});
data.olVersion = pkg.version;
data.filename = htmlName;
// add in script tag
const jsName = `${name}.js`;
let jsSource = chunk.modules[0].source;
if (data.cloak) {
for (const key in data.cloak) {
jsSource = jsSource.replace(new RegExp(key, 'g'), data.cloak[key]);
}
}
data.js = {
tag: `<script src="${this.common}.js"></script><script src="${jsName}"></script>`,
source: jsSource
};
// check for example css
const cssName = `${name}.css`;
const cssPath = path.join(dir, cssName);
let cssSource;
try {
cssSource = await readFile(cssPath, readOptions);
} catch (err) {
// pass
}
if (cssSource) {
data.css = {
tag: `<link rel="stylesheet" href="${cssName}">`,
source: cssSource
};
assets[cssName] = cssSource;
}
// add additional resources
if (data.resources) {
const resources = [];
const remoteResources = [];
const codePenResources = [];
for (let i = 0, ii = data.resources.length; i < ii; ++i) {
const resource = data.resources[i];
const remoteResource = resource.indexOf('//') === -1 ?
`https://openlayers.org/en/v${pkg.version}/examples/${resource}` : resource;
codePenResources[i] = remoteResource;
if (isJsRegEx.test(resource)) {
resources[i] = `<script src="${resource}"></script>`;
remoteResources[i] = `<script src="${remoteResource}"></script>`;
} else if (isCssRegEx.test(resource)) {
if (resource.indexOf('bootstrap.min.css') === -1) {
resources[i] = '<link rel="stylesheet" href="' + resource + '">';
}
remoteResources[i] = '<link rel="stylesheet" href="' +
remoteResource + '">';
} else {
throw new Error('Invalid value for resource: ' +
resource + ' is not .js or .css: ' + htmlName);
}
}
data.extraHead = {
local: resources.join('\n'),
remote: remoteResources.join('\n')
};
data.extraResources = data.resources.length ?
',' + codePenResources.join(',') : '';
}
const templatePath = path.join(this.templates, attributes.layout);
const templateSource = await readFile(templatePath, readOptions);
assets[htmlName] = handlebars.compile(templateSource)(data);
return [assets, data];
};
module.exports = ExampleBuilder;

View File

@@ -22,7 +22,8 @@
"changecase-src": "node tasks/filename-case-from-module.js",
"transform-examples": "jscodeshift --transform transforms/module.js examples",
"transform-test": "jscodeshift --transform transforms/module.js test",
"transform": "npm run changecase-src && npm run transform-src && npm run transform-examples && npm run transform-test && npm run lint -- --fix"
"transform": "npm run changecase-src && npm run transform-src && npm run transform-examples && npm run transform-test && npm run lint -- --fix",
"build-examples": "webpack --config examples/webpack/config.js"
},
"main": "dist/ol.js",
"repository": {
@@ -58,12 +59,15 @@
"coveralls": "3.0.0",
"debounce": "^1.1.0",
"eslint": "4.13.1",
"copy-webpack-plugin": "^4.0.1",
"eslint-config-openlayers": "7.0.0",
"eslint-plugin-openlayers-internal": "^3.1.0",
"expect.js": "0.3.1",
"front-matter": "^2.1.2",
"gaze": "^1.0.0",
"glob": "7.1.1",
"handlebars": "4.0.11",
"html-webpack-plugin": "^2.30.1",
"istanbul": "0.4.5",
"jquery": "3.2.1",
"jscodeshift": "^0.4.0",
@@ -85,7 +89,9 @@
"serve-files": "1.0.1",
"sinon": "4.1.3",
"slimerjs": "0.10.3",
"url-polyfill": "^1.0.7"
"url-polyfill": "^1.0.7",
"webpack": "^3.5.5",
"webpack-dev-server": "^2.7.1"
},
"eslintConfig": {
"extends": "openlayers",
@@ -138,8 +144,7 @@
]
}
},
"ext": [
{
"ext": [{
"module": "rbush"
},
{