Skip to content

Commit

Permalink
chore: upgrade to Docusaurus 2 beta (#19)
Browse files Browse the repository at this point in the history
- Upgrade to Docusaurus v2 beta
- Use `classic` Docusaurus preset
- List *features* on landing page
- Add missing content to Part 1 to fix broken links
- Use Yarn v1 as recommended by Docusaurus v2
- Fail if no build output is found

Related to #17.
  • Loading branch information
LayZeeDK authored Sep 4, 2021
1 parent 41ce534 commit 966744a
Show file tree
Hide file tree
Showing 40 changed files with 9,562 additions and 10,144 deletions.
28 changes: 12 additions & 16 deletions .github/workflows/website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ name: "RxJS Fundamentals website"

env:
NODE_OPTIONS: --max_old_space_size=6144
NPM_CACHE_PATH: ~/.npm
node-version: 14.x
website-package-name: website

Expand All @@ -17,36 +16,33 @@ jobs:
name: Build website
runs-on: ubuntu-latest

env:
working-directory: ./website

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ env.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.node-version }}

- run: npm config set cache $NPM_CACHE_PATH
working-directory: ${{ env.working-directory }}
- name: Cache NPM cache directory
- name: Variable-Yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache Yarn cache directory
uses: actions/cache@v2
with:
path: ${{ env.NPM_CACHE_PATH }}
key: ${{ runner.os }}-npm-${{ hashFiles('./website/package-lock.json') }}
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-node-${{ env.node-version }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-npm-
- run: npm ci
working-directory: ${{ env.working-directory }}
${{ runner.os }}-node-${{ env.node-version }}-yarn-
${{ runner.os }}-node-${{ env.node-version }}-
- run: yarn install
- name: Build website
run: npm run build
working-directory: ${{ env.working-directory }}
run: yarn build
- name: "Upload website build artifact"
uses: actions/upload-artifact@v2
with:
name: ${{ env.website-package-name }}
path: ${{ env.working-directory }}/build/rxjs-fundamentals-course
if-no-files-found: warn
path: ./build/
if-no-files-found: error

deploy:
name: Deploy website to GitHub Pages
Expand Down
27 changes: 19 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
website/node_modules/*
# Dependencies
/node_modules

# Production
/build

# Generated files
.docusaurus
.cache-loader

# Misc
.DS_Store
node_modules
lib/core/metadata.js
lib/core/MetadataBlog.js
website/translated_docs
website/build/
website/yarn.lock
website/i18n/*
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
3 changes: 3 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};
63 changes: 56 additions & 7 deletions docs/part-1.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
id: part-1
title: Part 1. RxJS: better async programming
title: "Part 1. RxJS: Better async programming"
---

# RxJS: better async programming
Expand Down Expand Up @@ -135,9 +135,9 @@ The thing is, `Promises` are only one particular case of async operation - a sin

Now, let's take a look at some click events on a button. We start listening to the click events, but there is no guarantee that the callback will be invoked any time soon, right? Now, 5 minutes later the user finally clicks the button, and our callback works and handles the event, but it is not "resolved". The user can continue clicking on the button as many times as they want, and we would still have to handle that click. And after the user finally goes to another page, we have to stop listening to the clicks, because there is no button any more. We cannot represent a stream of events like clicks with a Promise, because a Promise works once and is destroyed afterwards. But `Observable` Streams of RxJS give us the ability to create streams, listen to their events, handle error cases, and also, handle the situation when the stream completes - like when the user went to another page in our example. So, in this regard, we can treat `Observables` as a far more powerful version of Promises, which deals with multiple events rather than one instance.

![](RackMultipart20210318-4-1e6dc56_html_19b10da71535a078.png)
![Promise vs. RxJS](/img/part-1/promise-vs-rxjs.png)

(Picture by @thekiba_io)
_Image by Andrew Grekov._

Now lets take a closer look.

Expand Down Expand Up @@ -190,13 +190,33 @@ We will review some of these combinations and their use-cases in a later chapter

At first glance — Observables are just advanced Promises: Promises emits one value and complete (resolve), Observables emit 0, one or many values and complete as well (emit and complete are different actions). For HTTP service in AngularJS (where it used Promises) and Angular (where it uses Observables) provides only one value — so seems both frameworks work very similar in this case.

![](RackMultipart20210318-4-1e6dc56_html_fc59ddf18c80e00b.png)
```ts
// Observables in Angular 2+
const source$ = this.http.get('https://example.com');
source$.subscribe({
next: data => handleData(data), // Success handler
error: error => handleError(error), // Error handler
complete: () => completeHandler(), // Complete handler
});
```

but if you application is doing something more then 'Hello world' — please pay attention to the differences.

### #1 Eager vs Lazy

Take a look at the example below: ![](RackMultipart20210318-4-1e6dc56_html_533e70b36f8382d3.png)
Take a look at the example below:

```ts
// Promise-wrapped HTTP request
saveChanges(data) {
return $http.post('https://example.com', data);
}

// Observable-wrapped HTTP request
saveChanges(data) {
return this.http.post('https://example.com', data); // Does not send request!
}
```

When I call the `saveChanges` method — the first example with `Promise`-wrapped request will work as expected. But in seconds `Observable`-wrapped example nothing will happen because `Observables` are lazily-evaluated while `Promises` are eagerly-evaluated.

Expand All @@ -212,13 +232,42 @@ To keep an eye on it — use _rxjs-no-ignored-observable_ rule from [rxjs-tslint

Again, start with an example when we do search on a backend on input text change

![](RackMultipart20210318-4-1e6dc56_html_518aba158767f496.png)
```html
<input ngKeyup="onKeyUp($event)>
```
```ts
// Promise-wrapped HTTP request
saveChanges(event) {
const text = event.target.value;
$http.get('https://example.com?search=' + text)
.then(searchResult => showSearchResult(searchResult));
}
```
There is a drawback here — you cannot reject results of the previous request if the user continues typing (`debounce` make this problem a bit less but doesn't eliminate it). And yet another issue — race conditions are possible (when a later request result will come back faster then an earlier one — so we get an incorrect response displayed).
`Observables` can avoid this concern quite elegantly with [_ **switchMap** _](https://rxjs-dev.firebaseapp.com/api/operators/switchMap) operator
![](RackMultipart20210318-4-1e6dc56_html_f31260205a770974.png)
```html
<input id="search">
```
```ts
// Observable-wrapped HTTP requet
const inputElement = document.getElementById('search');
const search$ = fromEvent(inputElement, 'keyup');
ngOnInit() {
search$.pipe( // each time a new text value is emitted
switchMap(event => { // switchMap cancels previous request and sends a new one
const text = event.target.value;
return this.http.get('https://example.com?search=' + text);
})
).subscribe(newData => this.applyNewData(newData)); // Use new data
}
```
We will talk about `switchMap` and other higher-order-observable operators in the advanced RxJS course.
Expand Down
2 changes: 1 addition & 1 deletion docs/part-5.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
id: part-5
title: Part 5. Everything is a stream: Push-based architecture
title: "Part 5. Everything is a stream: Push-based architecture"
---

# Everything is a stream: Push-based architecture
Expand Down
123 changes: 123 additions & 0 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
const shadesOfPurpleTheme = require('prism-react-renderer/themes/shadesOfPurple');

const organizationName = 'this-is-learning';
const projectName = 'rxjs-fundamentals-course';
const title = 'RxJS Fundamentals';

// With JSDoc @type annotations, IDEs can provide config autocompletion
/** @type {import('@docusaurus/types').DocusaurusConfig} */
(module.exports = {
baseUrl: `/${projectName}/`,
favicon: 'img/favicon.ico',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
presets: [
[
'@docusaurus/preset-classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
blog: {
editUrl: `https://github.com/${organizationName}/${projectName}/edit/main/blog`,
showReadingTime: true,
},
docs: {
editUrl: `https://github.com/${organizationName}/${projectName}/edit/main`,
sidebarPath: require.resolve('./sidebars.js'),
},
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
}),
],
],
projectName,
tagline: 'An Open Learning course by This is Learning',
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
footer: {
copyright: ${new Date().getFullYear()} This is Learning. Licensed under CC BY-SA 4.0.`,
links: [
{
title: 'Open Learning',
items: [
{
label: 'RxJS Fundamentals',
to: '/docs/part-1',
},
{
label: 'This is Learning on DEV Community',
href: 'https://dev.to/this-is-learning',
},
{
label: 'This is Angular on DEV Community',
href: 'https://dev.to/this-is-angular',
},
],
},
{
title: 'Community',
items: [
{
label: 'This is Learning Community on Discord',
href: 'https://discord.gg/ygKzbrBtVn',
},
{
label: 'This is Learning on GitHub',
href: 'https://github.com/this-is-learning',
},
{
label: 'This is Angular on GitHub',
href: 'https://github.com/this-is-learning',
},
],
},
{
title: 'Social',
items: [
{
label: 'Star on GitHub',
href: `https://github.com/${organizationName}/${projectName}`,
},
{
label: 'This is Learning on Twitter',
href: 'https://twitter.com/Thisis_Learning',
},
{
label: 'This is Angular on Twitter',
href: 'https://twitter.com/Thisis_Angular',
},
],
},
],
style: 'dark',
},
navbar: {
items: [
{
docId: 'part-1',
label: 'Course',
position: 'left',
type: 'doc',
},
{
href: `https://github.com/${organizationName}/${projectName}`,
label: 'GitHub',
position: 'right',
},
],
logo: {
alt: title,
src: 'img/logo.png',
},
title,
},
prism: {
darkTheme: shadesOfPurpleTheme,
defaultLanguage: 'typescript',
theme: shadesOfPurpleTheme,
},
}),
title,
url: `https://${organizationName}.github.io`,
});
40 changes: 40 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "rxjs-fundamentals-course",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.6",
"@docusaurus/preset-classic": "2.0.0-beta.6",
"@mdx-js/react": "^1.6.21",
"@svgr/webpack": "^5.5.0",
"clsx": "^1.1.1",
"file-loader": "^6.2.0",
"prism-react-renderer": "^1.2.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"url-loader": "^4.1.1"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
26 changes: 26 additions & 0 deletions sidebars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/

module.exports = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],

// But you can create a sidebar manually
/*
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
},
],
*/
};
Loading

0 comments on commit 966744a

Please sign in to comment.