Skip to content

Commit

Permalink
feat: enable synchronous dom updates for SSR (#5615)
Browse files Browse the repository at this point in the history
* feat: enable synchronous dom updates for SSR

* Add branch pointing to config instead of package scripts (#5616)

# Pull Request

## 📖 Description

<!---
Provide some background and a description of your work.
What problem does this change solve?
Is this a breaking change, chore, fix, feature, etc?
-->
This change moves the branch specificity to beachball config to allow `yarn change` to target the feature branch and also removes the restriction on "major" change updates.

## ✅ Checklist

### General

<!--- Review the list and put an x in the boxes that apply. -->

- [ ] I have included a change request file using `$ yarn change`
- [ ] I have added tests for my changes.
- [x] I have tested my changes.
- [ ] I have updated the project documentation to reflect my changes.
- [x] I have read the [CONTRIBUTING](https://github.com/Microsoft/fast/blob/master/CONTRIBUTING.md) documentation and followed the [standards](https://www.fast.design/docs/community/code-of-conduct/#our-standards) for this project.

* feat: enable synchronous dom updates for SSR

* Change files

* Change files

Co-authored-by: Jane Chu <[email protected]>
Co-authored-by: EisenbergEffect <[email protected]>
  • Loading branch information
3 people authored Feb 17, 2022
1 parent 55bbb8a commit e097a1f
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: enable synchronous dom updates for SSR",
"packageName": "@microsoft/fast-element",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: update router to work with new template primitives",
"packageName": "@microsoft/fast-router",
"email": "[email protected]",
"dependentChangeType": "patch"
}
1 change: 1 addition & 0 deletions packages/web-components/fast-element/docs/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ export const DOM: Readonly<{
createInterpolationPlaceholder(index: number): string;
createCustomAttributePlaceholder(index: number): string;
createBlockPlaceholder(index: number): string;
setUpdateMode(isAsync: boolean): void;
queueUpdate(callable: Callable): void;
nextUpdate(): Promise<void>;
processUpdates(): void;
Expand Down
98 changes: 97 additions & 1 deletion packages/web-components/fast-element/src/dom.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function watchSetTimeoutForErrors<TError = any>() {
}

describe("The DOM facade", () => {
context("when updating DOM", () => {
context("when updating DOM asynchronously", () => {
it("calls task in a future turn", done => {
let called = false;

Expand Down Expand Up @@ -430,4 +430,100 @@ describe("The DOM facade", () => {
}, waitMilliseconds);
});
});

context("when updating DOM synchronously", () => {
beforeEach(() => {
DOM.setUpdateMode(false);
});

afterEach(() => {
DOM.setUpdateMode(true);
});

it("calls task immediately", () => {
let called = false;

DOM.queueUpdate(() => {
called = true;
});

expect(called).to.equal(true);
});

it("calls task.call method immediately", () => {
let called = false;

DOM.queueUpdate({
call: () => {
called = true;
}
});

expect(called).to.equal(true);
});

it("calls multiple tasks in order", () => {
const calls:number[] = [];

DOM.queueUpdate(() => {
calls.push(0);
});
DOM.queueUpdate(() => {
calls.push(1);
});
DOM.queueUpdate(() => {
calls.push(2);
});

expect(calls).to.eql([0, 1, 2]);
});

it("can schedule tasks recursively", () => {
const steps: number[] = [];

DOM.queueUpdate(() => {
steps.push(0);
DOM.queueUpdate(() => {
steps.push(2);
DOM.queueUpdate(() => {
steps.push(4);
});
steps.push(3);
});
steps.push(1);
});

expect(steps).to.eql([0, 1, 2, 3, 4]);
});

it(`can recurse ${maxRecursion} tasks deep`, () => {
let recurseCount = 0;
function go() {
if (++recurseCount < maxRecursion) {
DOM.queueUpdate(go);
}
}

DOM.queueUpdate(go);

expect(recurseCount).to.equal(maxRecursion);
});

it("throws errors immediately", () => {
const calls: number[] = [];
let caught: any;

try {
DOM.queueUpdate(() => {
calls.push(0);
throw 0;
});
} catch(error) {
caught = error;
}

expect(calls).to.eql([0]);
expect(caught).to.eql(0);
});
});
});
33 changes: 27 additions & 6 deletions packages/web-components/fast-element/src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const fastHTMLPolicy: TrustedTypesPolicy = $global.trustedTypes.createPolicy(
let htmlPolicy: TrustedTypesPolicy = fastHTMLPolicy;
const updateQueue: Callable[] = [];
const pendingErrors: any[] = [];
const rAF = $global.requestAnimationFrame;
let updateAsync = true;

function throwFirstError(): void {
if (pendingErrors.length) {
Expand All @@ -24,8 +26,13 @@ function tryRunTask(task: Callable): void {
try {
(task as any).call();
} catch (error) {
pendingErrors.push(error);
setTimeout(throwFirstError, 0);
if (updateAsync) {
pendingErrors.push(error);
setTimeout(throwFirstError, 0);
} else {
updateQueue.length = 0;
throw error;
}
}
}

Expand Down Expand Up @@ -128,16 +135,30 @@ export const DOM = Object.freeze({
return `<!--${marker}:${index}-->`;
},

/**
* Sets the update mode used by queueUpdate.
* @param isAsync Indicates whether DOM updates should be asynchronous.
* @remarks
* By default, the update mode is asynchronous, since that provides the best
* performance in the browser. Passing false to setUpdateMode will instead cause
* the queue to be immediately processed for each call to queueUpdate. However,
* ordering will still be preserved so that nested tasks do not run until
* after parent tasks complete.
*/
setUpdateMode(isAsync: boolean) {
updateAsync = isAsync;
},

/**
* Schedules DOM update work in the next async batch.
* @param callable - The callable function or object to queue.
*/
queueUpdate(callable: Callable) {
if (updateQueue.length < 1) {
$global.requestAnimationFrame(DOM.processUpdates);
}

updateQueue.push(callable);

if (updateQueue.length < 2) {
updateAsync ? rAF(DOM.processUpdates) : DOM.processUpdates();
}
},

/**
Expand Down

0 comments on commit e097a1f

Please sign in to comment.