diff --git a/examples/webpack/config.js b/examples/webpack/config.js index a7ea23a2a4..6121b4b66f 100644 --- a/examples/webpack/config.js +++ b/examples/webpack/config.js @@ -19,16 +19,25 @@ module.exports = { context: src, target: 'web', entry: entry, + stats: 'minimal', module: { rules: [{ + test: /\.js$/, use: { loader: 'buble-loader' }, - test: /\.js$/, include: [ path.join(__dirname, '..', '..', 'src'), path.join(__dirname, '..') ] + }, { + test: /\.js$/, + use: { + loader: path.join(__dirname, './worker-loader.js') + }, + include: [ + path.join(__dirname, '../../src/ol/worker') + ] }] }, optimization: { diff --git a/examples/webpack/worker-loader.js b/examples/webpack/worker-loader.js new file mode 100644 index 0000000000..b8d10eae09 --- /dev/null +++ b/examples/webpack/worker-loader.js @@ -0,0 +1,16 @@ +const build = require('../../tasks/serialize-workers').build; + +function loader() { + const callback = this.async(); + + build(this.resource, {minify: false}) + .then(chunk => { + for (const filePath in chunk.modules) { + this.addDependency(filePath); + } + callback(null, chunk.code); + }) + .catch(callback); +} + +module.exports = loader; diff --git a/examples/worker.html b/examples/worker.html new file mode 100644 index 0000000000..cc794a6a94 --- /dev/null +++ b/examples/worker.html @@ -0,0 +1,10 @@ +--- +layout: example.html +title: Worker +shortdesc: This example should be deleted. +docs: > + When you move the map, a message is sent to a worker. In response, the woker sends a + message back with the version identifier. +tags: "worker" +--- +
diff --git a/examples/worker.js b/examples/worker.js new file mode 100644 index 0000000000..5324c040b4 --- /dev/null +++ b/examples/worker.js @@ -0,0 +1,35 @@ +/* eslint-disable no-console */ + +import Map from '../src/ol/Map.js'; +import View from '../src/ol/View.js'; +import TileLayer from '../src/ol/layer/Tile.js'; +import OSM from '../src/ol/source/OSM.js'; +import {create as createVersionWorker} from '../src/ol/worker/version'; + + +const map = new Map({ + layers: [ + new TileLayer({ + source: new OSM() + }) + ], + target: 'map', + view: new View({ + center: [0, 0], + zoom: 2 + }) +}); + +const worker = createVersionWorker(); +worker.addEventListener('error', function(error) { + console.error('worker error', error); +}); + +worker.addEventListener('message', function(event) { + console.log('message from worker:', event.data); +}); + +map.on('moveend', function(event) { + const state = event.frameState.viewState; + worker.postMessage({zoom: state.zoom, center: state.center}); +}); diff --git a/package.json b/package.json index ac8671fcc7..332aafd0f7 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "build-index": "npm run build-package && node tasks/generate-index", "build-legacy": "shx rm -rf build && npm run build-index && webpack --config config/webpack-config-legacy-build.js && cleancss --source-map src/ol/ol.css -o build/legacy/ol.css", "copy-css": "shx cp src/ol/ol.css build/ol/ol.css", - "transpile": "shx rm -rf build/ol && shx mkdir -p build/ol && shx cp -rf src/ol build/ol/src && tsc --project config/tsconfig-build.json", + "transpile": "shx rm -rf build/ol && shx mkdir -p build/ol && shx cp -rf src/ol build/ol/src && node tasks/serialize-workers && tsc --project config/tsconfig-build.json", "typecheck": "tsc --pretty", "apidoc": "jsdoc -R config/jsdoc/api/index.md -c config/jsdoc/api/conf.json -P package.json -d build/apidoc" }, @@ -42,7 +42,7 @@ }, "devDependencies": { "@babel/core": "^7.4.0", - "@babel/preset-env": "^7.4.2", + "@babel/preset-env": "^7.4.4", "@openlayers/eslint-plugin": "^4.0.0-beta.2", "@types/arcgis-rest-api": "^10.4.4", "@types/geojson": "^7946.0.7", @@ -85,6 +85,11 @@ "pngjs": "^3.4.0", "proj4": "2.5.0", "puppeteer": "~1.16.0", + "rollup": "^1.12.0", + "rollup-plugin-babel": "^4.3.2", + "rollup-plugin-commonjs": "^10.0.0", + "rollup-plugin-node-resolve": "^5.0.0", + "rollup-plugin-terser": "^4.0.4", "serve-static": "^1.14.0", "shx": "^0.3.2", "sinon": "^7.3.2", diff --git a/src/ol/worker/version.js b/src/ol/worker/version.js new file mode 100644 index 0000000000..71abc3857f --- /dev/null +++ b/src/ol/worker/version.js @@ -0,0 +1,12 @@ +/** + * @module ol/worker/version + * A worker that responds to messages by posting a message with the version identifer. + */ +import {VERSION} from '../util'; + +onmessage = event => { + console.log('version worker received message:', event.data); // eslint-disable-line + postMessage(`version: ${VERSION}`); +}; + +export let create; diff --git a/tasks/serialize-workers.js b/tasks/serialize-workers.js new file mode 100644 index 0000000000..dbf7e12c1c --- /dev/null +++ b/tasks/serialize-workers.js @@ -0,0 +1,96 @@ +const path = require('path'); +const babel = require('rollup-plugin-babel'); +const resolve = require('rollup-plugin-node-resolve'); +const common = require('rollup-plugin-commonjs'); +const rollup = require('rollup'); +const terser = require('rollup-plugin-terser').terser; +const fse = require('fs-extra'); + +async function build(input, {minify = true} = {}) { + const plugins = [ + { + name: 'remove export let create', + transform(code, id) { + if (id !== input) { + return null; + } + return code.replace('export let create;\n', ''); + } + }, + common(), + resolve(), + babel({ + 'presets': [ + [ + '@babel/preset-env', + { + 'modules': false, + 'targets': 'last 2 version, not dead' + } + ] + ] + }), + { + name: 'serialize worker and export create function', + renderChunk(code) { + return ` + const source = ${JSON.stringify(code)}; + const blob = new Blob([source], {type: 'application/javascript'}); + const url = URL.createObjectURL(blob); + export function create() { + return new Worker(url); + } + `; + } + } + ]; + + if (minify) { + plugins.push(terser()); + } + + const bundle = await rollup.rollup({input, plugins}); + const {output} = await bundle.generate({format: 'es'}); + + if (output.length !== 1) { + throw new Error(`Unexpected output length: ${output.length}`); + } + + const chunk = output[0]; + if (chunk.isAsset) { + throw new Error('Expected a chunk, got an asset'); + } + + return chunk; +} + +exports.build = build; + + +/** + * Creates modules with inlined versions of the worker sources. These modules + * export a `create` function for creating a worker. + */ +async function main() { + const inputDir = path.join(__dirname, '../src/ol/worker'); + const outputDir = path.join(__dirname, '../build/ol/src/worker'); + + await fse.ensureDir(outputDir); + + const entries = await fse.readdir(inputDir); + for (const entry of entries) { + if (!entry.endsWith('.js')) { + continue; + } + + const chunk = await build(path.join(inputDir, entry)); + await fse.writeFile(path.join(outputDir, entry), chunk.code); + } +} + +if (require.main === module) { + main().catch(err => { + process.stderr.write(`${err.stack}\n`); + process.exit(1); + }); +} diff --git a/test/karma.config.js b/test/karma.config.js index e1be311201..fbb0fb011f 100644 --- a/test/karma.config.js +++ b/test/karma.config.js @@ -86,6 +86,14 @@ module.exports = function(karma) { }, include: path.resolve('src/ol/'), exclude: path.resolve('node_modules/') + }, { + test: /\.js$/, + use: { + loader: path.join(__dirname, '../examples/webpack/worker-loader.js') + }, + include: [ + path.join(__dirname, '../src/ol/worker') + ] } ] } diff --git a/test/spec/ol/worker/version.test.js b/test/spec/ol/worker/version.test.js new file mode 100644 index 0000000000..2dab618dfb --- /dev/null +++ b/test/spec/ol/worker/version.test.js @@ -0,0 +1,32 @@ +import {create} from '../../../../src/ol/worker/version.js'; +import {VERSION} from '../../../../src/ol/util.js'; + + +describe('ol/worker/version', function() { + + let worker; + beforeEach(function() { + worker = create(); + }); + + afterEach(function() { + if (worker) { + worker.terminate(); + } + worker = null; + }); + + describe('messaging', function() { + it('responds with the version', function(done) { + worker.addEventListener('error', done); + + worker.addEventListener('message', function(event) { + expect(event.data).to.equal('version: ' + VERSION); + done(); + }); + + worker.postMessage('test message'); + }); + }); + +}); diff --git a/tsconfig.json b/tsconfig.json index 4de40de1b2..6e378a37be 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ "target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "lib": ["es2017", "dom"], /* Specify library files to be included in the compilation. */ + "lib": ["es2017", "dom", "webworker"], /* Specify library files to be included in the compilation. */ "allowJs": true, /* Allow javascript files to be compiled. */ "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */