Script to build examples
This commit is contained in:
@@ -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.
|
||||
|
||||
1
examples/templates/example-verbatim.html
Normal file
1
examples/templates/example-verbatim.html
Normal file
@@ -0,0 +1 @@
|
||||
{{{ contents }}}
|
||||
122
examples/templates/example.html
Normal file
122
examples/templates/example.html
Normal file
@@ -0,0 +1,122 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" type="text/css">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" type="text/css">
|
||||
<link rel="stylesheet" href="./resources/prism/prism.css" type="text/css">
|
||||
<link rel="stylesheet" href="../css/ol.css" type="text/css">
|
||||
<link rel="stylesheet" href="./resources/layout.css" type="text/css">
|
||||
{{{ extraHead.local }}}
|
||||
{{{ css.tag }}}
|
||||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=fetch,requestAnimationFrame,Element.prototype.classList,URL"></script>
|
||||
<script src="./resources/zeroclipboard/ZeroClipboard.min.js"></script>
|
||||
<title>{{ title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="navbar" role="navigation">
|
||||
<div class="container">
|
||||
<div class="display-table pull-left" id="navbar-logo-container">
|
||||
<a class="navbar-brand" href="./"><img src="./resources/logo-70x70.png"> OpenLayers Examples</a>
|
||||
</div>
|
||||
<!-- menu items that get hidden below 768px width -->
|
||||
<nav class='collapse navbar-collapse navbar-responsive-collapse'>
|
||||
<ul class="nav navbar-nav pull-right">
|
||||
<li><a href="../doc">Docs</a></li>
|
||||
<li><a class="active" href="index.html">Examples</a></li>
|
||||
<li><a href="../apidoc">API</a></li>
|
||||
<li><a href="https://github.com/openlayers/openlayers">Code</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<div id="latest-check" class="alert alert-warning alert-dismissible" role="alert" style="display:none">
|
||||
<button id="latest-dismiss" type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
This example uses OpenLayers v<span>{{ olVersion }}</span>. The <a id="latest-link" href="#" class="alert-link">latest</a> is v<span id="latest-version"></span>.
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<h4 id="title">{{ title }}</h4>
|
||||
{{{ contents }}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<p id="shortdesc">{{ shortdesc }}</p>
|
||||
<div id="docs">{{ md docs }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div id="source-controls">
|
||||
<a id="copy-button"><i class="fa fa-clipboard"></i> Copy</a>
|
||||
<a id="codepen-button"><i class="fa fa-codepen"></i> Edit</a>
|
||||
</div>
|
||||
<form method="POST" id="codepen-form" target="_blank" action="https://codepen.io/pen/define/">
|
||||
<textarea class="hidden" name="title">{{ title }}</textarea>
|
||||
<textarea class="hidden" name="description">{{ shortdesc }}</textarea>
|
||||
<textarea class="hidden" name="js">{{ js.source }}</textarea>
|
||||
<textarea class="hidden" name="css">{{ css.source }}</textarea>
|
||||
<textarea class="hidden" name="html">{{ contents }}</textarea>
|
||||
<input type="hidden" name="resources" value="https://openlayers.org/en/v{{ olVersion }}/css/ol.css,https://openlayers.org/en/v{{ olVersion }}/build/ol.js{{ extraResources }}">
|
||||
<input type="hidden" name="data">
|
||||
</form>
|
||||
<pre><code id="example-source" class="language-markup"><!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
<link rel="stylesheet" href="https://openlayers.org/en/v{{ olVersion }}/css/ol.css" type="text/css">
|
||||
<!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
|
||||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
|
||||
<script src="https://openlayers.org/en/v{{ olVersion }}/build/ol.js"></script>{{#if extraHead.remote}}
|
||||
{{ indent extraHead.remote spaces=4 }}{{/if}}{{#if css.source}}
|
||||
<style>
|
||||
{{ indent css.source spaces=6 }} </style>{{/if}}
|
||||
</head>
|
||||
<body>
|
||||
{{ indent contents spaces=4 }} <script>
|
||||
{{ indent js.source spaces=6 }} </script>
|
||||
</body>
|
||||
</html></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="./resources/common.js"></script>
|
||||
<script src="./resources/prism/prism.min.js"></script>
|
||||
{{{ js.tag }}}
|
||||
</body>
|
||||
<script>
|
||||
var packageUrl = 'https://raw.githubusercontent.com/openlayers/openlayers.github.io/build/package.json';
|
||||
fetch(packageUrl).then(function(response) {
|
||||
return response.json();
|
||||
}).then(function(json) {
|
||||
var latestVersion = json.version;
|
||||
document.getElementById('latest-version').innerHTML = latestVersion;
|
||||
var url = window.location.href;
|
||||
var branchSearch = url.match(/\/([^\/]*)\/examples\//);
|
||||
var cookieText = 'dismissed=-' + latestVersion + '-';
|
||||
var dismissed = document.cookie.indexOf(cookieText) != -1;
|
||||
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');
|
||||
a.href = response.status == 200 ? link : '../../latest/examples/';
|
||||
});
|
||||
var latestCheck = document.getElementById('latest-check');
|
||||
latestCheck.style.display = '';
|
||||
document.getElementById('latest-dismiss').onclick = function() {
|
||||
latestCheck.style.display = 'none';
|
||||
document.cookie = cookieText;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
19
examples/templates/readme.md
Normal file
19
examples/templates/readme.md
Normal file
@@ -0,0 +1,19 @@
|
||||
This folder contains example templates. These templates are used to build the examples in the `examples/` folder. The resulting examples are written to the `build/examples` folder.
|
||||
|
||||
Although the main purpose of these examples is to demonstrate how to use the API, they also serve other purposes in the development cycle, and so are not exactly as they would be in normal application code:
|
||||
|
||||
* every time the library changes, they are compiled together with the library as a basic check that they remain in sync with the library
|
||||
|
||||
* they use a special loader script to enable defining at run time which build mode (raw/debug/advanced) to use
|
||||
|
||||
To enable this, examples have the following, not needed in application code:
|
||||
|
||||
* each html file loads `loader.js`; application code would not need this, but would instead load the appropriate library build file, either a hosted version or a custom build
|
||||
|
||||
* each js file starts with `goog.require` functions, used by the compiler; application code would only have these if the code is to be compiled together with the library and/or Closure library
|
||||
|
||||
* some js files use type definitions (comments with @type tags); these are also used by the compiler, and are only needed if the code is to be compiled together with the library
|
||||
|
||||
* html files load `resources/common.js` and some scripts use `common.getRendererFromQueryString()` to set the map renderer; application code would not need these
|
||||
|
||||
At the bottom of each example generated in the `build/examples` folder, a modified version of its source code is shown. That modified version can be run standalone and is usually used as starting point for users to extend examples into their own application.
|
||||
16
examples/webpack/.eslintrc
Normal file
16
examples/webpack/.eslintrc
Normal 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"
|
||||
}]
|
||||
}
|
||||
}
|
||||
43
examples/webpack/config.js
Normal file
43
examples/webpack/config.js
Normal 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')
|
||||
}
|
||||
};
|
||||
211
examples/webpack/example-builder.js
Normal file
211
examples/webpack/example-builder.js
Normal 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;
|
||||
Reference in New Issue
Block a user