Skip to content

Commit

Permalink
Add library build that take worker instance
Browse files Browse the repository at this point in the history
  • Loading branch information
mizchi committed Apr 29, 2020
1 parent 0c9eeb3 commit 665953e
Show file tree
Hide file tree
Showing 13 changed files with 411 additions and 1 deletion.
3 changes: 2 additions & 1 deletion config/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@

import MainThreadBuilds from './rollup.main-thread.js';
import WorkerThreadBuilds from './rollup.worker-thread.js';
import LibBuilds from './rollup.lib.js';

export default [...MainThreadBuilds, ...WorkerThreadBuilds];
export default [...MainThreadBuilds, ...WorkerThreadBuilds, ...LibBuilds];
68 changes: 68 additions & 0 deletions config/rollup.lib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Copyright 2018 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import compiler from '@ampproject/rollup-plugin-closure-compiler';
import { terser } from 'rollup-plugin-terser';
import replace from '@rollup/plugin-replace';
import { babelPlugin } from './rollup.plugins.js';
import path from 'path';

// Compile plugins should always be added at the end of the plugin list.
const compilePlugins = [
compiler({
env: 'CUSTOM',
}),
terser(),
];

export default [
{
input: path.join(__dirname, '../output/main-thread/lib.js'),
output: {
file: 'dist/lib/main.mjs',
format: 'esm',
sourcemap: true,
},
plugins: [
replace({
WORKER_DOM_DEBUG: false,
}),
babelPlugin({
transpileToES5: false,
allowConsole: false,
}),
...compilePlugins,
],
},
{
input: path.join(__dirname, '../output/worker-thread/lib.js'),
output: {
file: 'dist/lib/worker.mjs',
format: 'esm',
sourcemap: true,
},
plugins: [
replace({
WORKER_DOM_DEBUG: false,
}),
babelPlugin({
transpileToES5: false,
allowConsole: false,
}),
...compilePlugins,
],
},
];
1 change: 1 addition & 0 deletions demo/as-webpack-lib/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
41 changes: 41 additions & 0 deletions demo/as-webpack-lib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# worker-dom as package with webpack

## Webpack settings

- Add `@ampproject/worker-dom`
- Add `worker-plugin` or `worker-loader` to create worker on webpack

In this README, we use `worker-plugin`.

```js
// webpack.config.js
const WorkerPlugin = require("worker-plugin");
module.exports = {
// ...
plugins: [new WorkerPlugin()]
}
```

## Example

```js
// worker.js
import { ready } from "@ampproject/worker-dom/dist/lib/worker";

ready.then(() =>{
document.body.firstChild.textContent = 'hello from worker mutate';
});
```

```js
// main.js
import { attachWorker } from "@ampproject/worker-dom/dist/lib/main";

// Create worker by your own way
// This code uses worker-plugin
const worker = new Worker("./worker.js", { type: "module" });

// attach worker to dom
attachWorker(document.querySelector('#root'), worker);
```

Empty file added demo/as-webpack-lib/index.html
Empty file.
13 changes: 13 additions & 0 deletions demo/as-webpack-lib/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"private": true,
"scripts": {
"build": "webpack --mode development"
},
"devDependencies": {
"html-webpack-plugin": "^4.2.1",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"worker-plugin": "^4.0.2"
}
}
11 changes: 11 additions & 0 deletions demo/as-webpack-lib/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>

</body>
</html>
14 changes: 14 additions & 0 deletions demo/as-webpack-lib/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { attachWorker } from '@ampproject/worker-dom/dist/lib/main.mjs';

const el = document.createElement('main');
el.innerHTML = `
<div>
<h1>main</h1>
<button>click</button>
</div>
`;
document.body.appendChild(el);

const worker = new Worker('./worker.js', { type: 'module' });

attachWorker(el, worker);
18 changes: 18 additions & 0 deletions demo/as-webpack-lib/src/worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ready } from '@ampproject/worker-dom/dist/lib/worker.mjs';

async function run() {
await ready;

// dom mutation
const el = document.createElement('h2');
el.textContent = 'hello from worker';
document.body.appendChild(el);

// handler
const button = document.querySelector('button');
button.onclick = (ev) => {
console.log('button:onclick', ev);
};
}

run();
34 changes: 34 additions & 0 deletions demo/as-webpack-lib/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright 2018 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const path = require('path');
const HTMLPlugin = require('html-webpack-plugin');
const WorkerPlugin = require('worker-plugin');

module.exports = {
mode: 'development',
resolve: {
alias: {
'@ampproject/worker-dom': path.join(__dirname, '../../'),
},
},
plugins: [
new WorkerPlugin(),
new HTMLPlugin({
template: path.join(__dirname, 'src/index.html'),
}),
],
};
36 changes: 36 additions & 0 deletions src/main-thread/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { HydrateableNode } from '../transfer/TransferrableNodes';
import { TransferrableKeys } from '../transfer/TransferrableKeys';

export function buildWorkerString(
workerDOMScript: string,
authorScript: string,
authorURL: string,
strings: string[],
skeleton: HydrateableNode,
cssKeys: string[],
globalEventHandlerKeys: string[],
size: [number, number],
localStorageData: { [key: string]: string },
sessionStorageData: { [key: string]: string },
): string {
return `'use strict';
(function(){
${workerDOMScript}
self['window'] = self;
var workerDOM = WorkerThread.workerDOM;
WorkerThread.hydrate(
workerDOM.document,
${JSON.stringify(strings)},
${JSON.stringify(skeleton)},
${JSON.stringify(cssKeys)},
${JSON.stringify(globalEventHandlerKeys)},
${JSON.stringify(size)},
${JSON.stringify(localStorageData)},
${JSON.stringify(sessionStorageData)}
);
workerDOM.document[${TransferrableKeys.observe}](this);
Object.keys(workerDOM).forEach(function(k){self[k]=workerDOM[k]});
}).call(self);
${authorScript}
//# sourceURL=${encodeURI(authorURL)}`;
}
138 changes: 138 additions & 0 deletions src/main-thread/lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* Copyright 2018 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { MutationFromWorker, MessageType, MessageFromWorker, MessageToWorker } from '../transfer/Messages';
import { MutatorProcessor } from './mutator';
import { NodeContext } from './nodes';
import { StringContext } from './strings';
import { TransferrableKeys } from '../transfer/TransferrableKeys';
import { InboundWorkerDOMConfiguration, normalizeConfiguration, WorkerDOMConfiguration } from './configuration';
import { WorkerContext } from './worker';
import { ObjectContext } from './object-context';
import { readableMessageToWorker, readableHydrateableRootNode } from './debugging';
import { createHydrateableRootNode } from './serialize';

const ALLOWABLE_MESSAGE_TYPES = [MessageType.MUTATE, MessageType.HYDRATE];

export function attachWorker(
baseElement: HTMLElement,
worker: Worker,
config: InboundWorkerDOMConfiguration = {
authorURL: '[external-instance]',
domURL: '[external-instance]',
},
) {
const stringContext = new StringContext();
const objectContext = new ObjectContext();
const nodeContext = new NodeContext(stringContext, baseElement);
const normalizedConfig = normalizeConfiguration(config);
const workerContext = (new WorkerContextFromInstance(baseElement as HTMLElement, nodeContext, worker, normalizedConfig) as any) as WorkerContext;
const mutatorContext = new MutatorProcessor(stringContext, nodeContext, workerContext, normalizedConfig, objectContext);

workerContext.worker.onmessage = (message: MessageFromWorker) => {
const { data } = message;
if (!ALLOWABLE_MESSAGE_TYPES.includes(data[TransferrableKeys.type])) {
return;
}
mutatorContext.mutate(
(data as MutationFromWorker)[TransferrableKeys.phase],
(data as MutationFromWorker)[TransferrableKeys.nodes],
(data as MutationFromWorker)[TransferrableKeys.strings],
new Uint16Array(data[TransferrableKeys.mutations]),
);

if (config.onReceiveMessage) {
config.onReceiveMessage(message);
}
};
}

// TODO: Refactor with original
class WorkerContextFromInstance {
private [TransferrableKeys.worker]: Worker;
private nodeContext: NodeContext;
private config: WorkerDOMConfiguration;

/**
* @param baseElement
* @param nodeContext
* @param workerDOMScript
* @param authorScript
* @param config
*/
constructor(baseElement: HTMLElement, nodeContext: NodeContext, worker: Worker, config: WorkerDOMConfiguration) {
const selfAsWorkerContext = (this as any) as WorkerContext;
this.nodeContext = nodeContext;
this.config = config;

const { skeleton, strings } = createHydrateableRootNode(baseElement, config, selfAsWorkerContext);
const cssKeys: Array<string> = [];
const globalEventHandlerKeys: Array<string> = [];
// TODO(mizchi): Can not serialize on postMessage
// TODO(choumx): Sync read of all localStorage and sessionStorage a possible performance bottleneck?
// const localStorageData = config.sanitizer ? config.sanitizer.getStorage(StorageLocation.Local) : window.localStorage;
// const sessionStorageData = config.sanitizer ? config.sanitizer.getStorage(StorageLocation.Session) : window.sessionStorage;

for (const key in baseElement.style) {
cssKeys.push(key);
}
for (const key in baseElement) {
if (key.startsWith('on')) {
globalEventHandlerKeys.push(key);
}
}

this[TransferrableKeys.worker] = worker;
worker.postMessage({
__init__: [
strings,
skeleton,
cssKeys,
globalEventHandlerKeys,
[window.innerWidth, window.innerHeight],
{}, // localStorage
{}, // sessionStorage
],
});

if (WORKER_DOM_DEBUG) {
console.info('debug', 'hydratedNode', readableHydrateableRootNode(baseElement, config, selfAsWorkerContext));
}
if (config.onCreateWorker) {
config.onCreateWorker(baseElement, strings, skeleton, cssKeys);
}
}

/**
* Returns the private worker.
*/
get worker(): Worker {
return this[TransferrableKeys.worker];
}

/**
* @param message
*/
messageToWorker(message: MessageToWorker, transferables?: Transferable[]) {
if (WORKER_DOM_DEBUG) {
console.info('debug', 'messageToWorker', readableMessageToWorker(this.nodeContext, message));
}
if (this.config.onSendMessage) {
this.config.onSendMessage(message);
}
this.worker.postMessage(message, transferables || []);
}
}
Loading

0 comments on commit 665953e

Please sign in to comment.