Merge branch 'master' of github.com:Microsoft/frontend-bootcamp

This commit is contained in:
Ken
2019-03-04 19:36:51 -08:00
8 changed files with 85 additions and 80 deletions

View File

@@ -2,10 +2,10 @@
A simple web page is rendered on the screen via the following steps.
_There are many sub-steps in this process, but these are the highlights._
> There are many sub-steps in this process, but these are the highlights.
1. You instruct the browser which web page you'd like to see
2. The browser looks up the site in a "DNS Server"
2. The browser looks up the site on a DNS server
- This is like a big phone book for website server addresses
3. The browser asks the server to send over a specific page of the website, such as `developer.mozilla.org/filename.html` or `developer.mozilla.org`
- If asked for a "root"-level address, most servers will return `<root>/index.html`
@@ -23,7 +23,7 @@ _There are many sub-steps in this process, but these are the highlights._
# HTML Demo
HTML tags are the basis of all web applications. They give the page structure, and define the content within.
HTML tags are the basis of all web applications. They give the page structure and define the content within.
An HTML tag takes the following form:
@@ -31,9 +31,9 @@ An HTML tag takes the following form:
<tag class="foo" onclick="myFunction()" otherAttributes="values"> </tag>
```
HTML tags can also be nested to create a tree that we call the [Document Object Model](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction)
HTML tags can also be nested to create a tree that we call the [Document Object Model](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction).
The [HTML demo page](https://microsoft.github.io/frontend-bootcamp/step1-01/html-demo/html-demo.html) is a large collection of HTML elements that you will come across during development. The full list of elements can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element).
The [HTML demo page](https://microsoft.github.io/frontend-bootcamp/step1-01/demo) shows a large collection of HTML elements that you will come across during development. The full list of elements can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element).
## Sample Website

View File

@@ -20,7 +20,7 @@ The power of HTML is its ability to represent complex information in a way that
1. Create a recipe page to host our recipe
2. Use header, main, footer, headings (h1/h2 etc), paragraphs, lists
3. Use ordered and unordered lists appropriately
4. Add the `baked_beans.jpg` image included in this folder: `https://raw.githubusercontent.com/Microsoft/frontend-bootcamp/master/step1-01/exercise/baked_beans.jpg`
4. Add the `baked_beans.jpg` image: https://raw.githubusercontent.com/Microsoft/frontend-bootcamp/master/step1-01/exercise/baked_beans.jpg
5. Add an anchor tag around 'Wisconsin Beer Brats'
> Note that CodePen takes care of the `HTML` and `Body` tags, so you can simply start with the content

View File

@@ -6,10 +6,10 @@ Now that we've gone over adding HTML tags to the page, let's cover adding styles
- Typography
- Colors
- Appearance (Corners, Borders, Decorations)
- Appearance (corners, borders, decorations)
- Layout
- Position
- Inline vs Block
- Display format: inline vs block
- Animations
- and [many more](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference)
@@ -43,4 +43,4 @@ Here's a more detailed view from [Chris Eppstein](https://twitter.com/chriseppst
<img src="https://raw.githubusercontent.com/Microsoft/frontend-bootcamp/master/assets/css-syntax.png"/>
Selectors can be a single tag, class, ID, or attribute. It can also be a [combination](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Combinators_and_multiple_selectors) of those elements.
A selector can be a single tag, class, ID, or attribute. It can also be a [combination](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Combinators_and_multiple_selectors) of those elements.

View File

@@ -1,8 +1,8 @@
# JavaScript Demo
It's entirely possible to create a website with nothing but HTML and CSS, but as soon as you want user interaction other than links and forms, you'll need to reach for JavaScript, the scripting language of the web. Fortunately, JavaScript has grown up quite a bit since it was introduced in the 90s, and now runs just about everything: web applications, mobile applications, native applications, servers, robots and rocket ships.
It's entirely possible to create a website with nothing but HTML and CSS, but as soon as you want user interaction other than links and forms, you'll need to reach for JavaScript, the scripting language of the web. Fortunately, JavaScript has grown up quite a bit since it was introduced in the '90s, and now runs just about everything: web applications, mobile applications, native applications, servers, robots and rocket ships.
In this demo we are going to cover a few of the core basics of the language that will help us when we start writing our TODO app. At the end of this demo we will be able to display the number of the letter "a"s in our email input. Here's the markup we're working with:
In this demo we are going to cover a few core basics of the language that will help us when we start writing our todo app. At the end of this demo we will be able to count and display the number of the letter "a"s in our email input. Here's the markup we're working with:
```html
<div id="contact-form">
@@ -24,8 +24,6 @@ By the end of the demo we'll have covered the following:
We can create a new variable with the keywords `var`, `let`, `const` and use them within our application. These variables can contain one of the following types of values:
> Use `const` for variables you never expect to change, and `let` for anything else. `var` is mostly out of fasion.
- **boolean**: `true`, `false`
- **number**: `1`, `3.14`
- **string**: `'single quotes'`, `"double quotes"`, or `` `backticks` ``
@@ -35,20 +33,22 @@ We can create a new variable with the keywords `var`, `let`, `const` and use the
- **null**
- **undefined**
> [When to use `var`/`let`/`const`?](https://stackoverflow.com/questions/762011/whats-the-difference-between-using-let-and-var-to-declare-a-variable-in-jav) Use `const` for variables you never expect to change, and `let` for anything else. `var` is mostly no longer used. See the link for more details about how each works.
### Variable Examples
```js
let myBoolean = true;
let myNumber = 5;
let myString = `Using backticks I can reuse other variables ${myNumber}`;
let myArray = [1, 'cat', false, myString];
let myObject = { key1: 'value1', anotherKey: myArray };
let myFunction = function(myNumberParam) {
const myBoolean = true;
const myNumber = 5;
const myString = `Using backticks I can reuse other variables ${myNumber}`;
const myArray = [1, 'cat', false, myString];
const myObject = { key1: 'value1', anotherKey: myArray };
const myFunction = function(myNumberParam) {
console.log(myNumber + myNumberParam);
};
```
> JavaScript is a loosely typed (dynamic) language, so if you initially store a number in a variable (`let myVar = 0`), you can change it to contain a string by simply writing `myVar = 'hello'` without any trouble.
> JavaScript is a dynamically typed language, so if you initially store a number in a variable (`let myVar = 0`), you can change it to contain a string by simply writing `myVar = 'hello'` without any trouble.
### Adding Variables
@@ -62,9 +62,7 @@ const match = 'a';
Functions are reusable pieces of functionality. Functions can take inputs (parameters) and return values (or neither). Functions can be called from within your program, from within other functions, or invoked from within the DOM itself.
In our example we'll create a function called `displayMatches` (camelCase is typical for functions) and we'll invoke this function every time that our submit button is clicked. For now we'll simply have our function call `alert("I'm Clicked")`, which is a function that creates an alert in your browser.
> Note that we want to place `matches` inside of the function so that it resets to 0 each time the function is called
In our example we'll create a function called `displayMatches` (camelCase is typical for functions) and we'll invoke this function every time that our submit button is clicked. For now we'll simply have our function call `alert("I'm Clicked")`, which is a function that displays an alert message box in your browser.
```js
function displayMatches() {
@@ -74,9 +72,9 @@ function displayMatches() {
## Events
Functions on their own don't have any affect on the page. When I declare `function displayMatches()` I have only defined the function, I haven't actually triggered it.
Functions on their own don't have any effect on the page. When I declare `function displayMatches()` I have only defined the function; I haven't actually executed it.
To execute a function we need to attach it to an event. There are a number of possible events: keyboard strokes, mouse clicks, document loading etc...
To execute a function we need to attach it to an event. There are a number of possible events: keyboard strokes, mouse clicks, document loading, and more.
### Add Event Listeners
@@ -92,11 +90,11 @@ window.addEventListener('click', function() {
});
```
> The `window` is a reference to the entire HTML document
> [`window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) is a reference to the entire window containing the HTML document.
### Global Event Handlers
If this feels a little verbose, you're not alone. Many of the [most common event types](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers) are added as element properties. This way we can set properties like `onload` or `onclick` like this:
If you think this feels a little verbose, you're not alone. Many of the [most common event types](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers) are available as element properties. This way we can set properties like `onload` or `onclick` like this:
```js
window.onload = function() {
@@ -107,22 +105,22 @@ window.onclick = function() {
};
```
> Note that only a single function can be assigned to `onload`, but many eventListeners can be added to `load`
> Note that only a single function can be assigned to `onload`, but many event listeners can be added for `load`.
In our example we want to trigger a function based on the click of a button. To do this, we first need to get a reference to the button. We can use `querySelector` to get that reference. And then we can set its `onclick` value just like above.
In our example, we want to trigger a function when a button is clicked. To do this, we first need to get a reference to the button. We can use the [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) method of the browser-provided [`document`](https://developer.mozilla.org/en-US/docs/Web/API/Document) global variable to get that reference, and then we can set its `onclick` value just like above.
```js
const button = document.querySelector('.submit');
button.onclick = displayMatches();
```
You can also combine these together like this:
You can also combine the two statements together like this:
```js
document.querySelector('.submit').onclick = displayMatches;
```
Wire this up and see you function in action!
Reload the page and click the button to see your function in action!
## Iteration
@@ -154,13 +152,13 @@ function displayMatches() {
}
```
> In JavaScript, it's safest to use strict `===` for comparisons, because `==` will try to convert the operands to the same type. For example, `"1" == 1` is true whereas `"1" === 1` is false, but the behavior in certain other cases is [not what you'd expect](https://www.youtube.com/watch?v=et8xNAc2ic8). (See [this video](https://www.destroyallsoftware.com/talks/wat) for more strange JavaScript behavior.)
> In JavaScript, it's safest to use strict `===` for comparisons, because `==` will try to convert the operands to the same type. For example, `"1" == 1` converts `"1"` to a number and returns true. This result makes decent sense, but the behavior in certain other cases is [not what you'd expect](https://www.youtube.com/watch?v=et8xNAc2ic8). (See [this video](https://www.destroyallsoftware.com/talks/wat) for more strange JavaScript behavior.)
## Interacting with the DOM
Now that we have a function performing all of our logic, it's time to connect this to our DOM by using some of the browser's built-in functions.
First we need to get a reference to the email field in our app's DOM. To do this, I've added an `id` to the input, and we will call one of JavaScript's oldest methods (IE 5.5), `getElementById`, which we find on the browser-provided `document` global variable. This function will return a reference to that input, and we can store it in the `email` variable.
First we need to get a reference to the email field in our app's DOM. To do this, I've added an `id` to the input, and we'll find the element using [`getElementById`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById) from the `document` global variable. This function will return a reference to that input, and we can store it in the `email` variable.
```js
function displayMatches() {
@@ -170,7 +168,7 @@ function displayMatches() {
}
```
Since what we're actually after is the value of the input field, we can set our `text` variable to the string assigned to the email input's `value` key. To see this in action, in Chrome you can right click on the console message created by the code above, choose "save as variable" and then type `variableName.value`.
Since what we're actually after is the value of the input field, we can set our `text` variable to the string contained in the email input's `value` key. To see this in action, in Chrome you can right click on the console message created by the code above, choose "save as variable" and then type `variableName.value`.
```js
function displayMatches() {
@@ -181,9 +179,9 @@ function displayMatches() {
}
```
### Returning the values to the DOM
### Writing values back to the DOM
Now that we've read values from the DOM and fed that into our matching logic, we are ready to return the number of matches to our app. To do this we first need to grab a reference to our submit button, and since this button has no `id` we are going to use the more modern (IE8+) `querySelector` to get it. This function takes any valid CSS selector and returns the first match found.
Now that we've read values from the DOM and fed that into our matching logic, we are ready to return the number of matches to our app. To do this we first need to grab a reference to our submit button, and since this button has no `id`, we'll use `querySelector` to get it. This function takes any valid CSS selector and returns the first match found.
```js
function displayMatches() {

View File

@@ -1,9 +1,11 @@
1. Create a function named `getFavs` and set its contents to `alert('clicked')`
# Exercise
1. Create a function named `getFavs`. Inside, run `alert('clicked')`.
2. Create a variable `button` and set it to a reference to our button by using `document.querySelector('button')`
3. Add a click event listener to the button that calls `getFavs`. Click the button and make sure it calls our alert.
4. Replace the alert with a new `favList` variable set to an empty array: `[]`
5. Create a const variable `inputs` set to all of the inputs on the page. `querySelectorAll` will help here
3. Add a click event listener to the button that calls `getFavs`. Click the button and make sure the alert is displayed.
4. Replace the `alert` call with a new `favList` variable set to an empty array: `[]`
5. Create a const variable `inputs` set to all of the inputs on the page. [`querySelectorAll`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) will help here.
6. Iterate over all of the inputs using `for (const input of inputs) {}`
7. For each iteration use an `if` statement to check if `input.checked` is equal to true
8. If the above tests passes, push the `input.parentNode.textContent` onto the `favList` array. Pass that text into `favList.push()` as the parameter to add it to the array
7. In each iteration, use an `if` statement to check if `input.checked` is equal to true
8. If the above tests passes, push the `input.parentNode.textContent` onto the `favList` array by passing the text as a parameter to `favList.push()` ([`push](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push) is a built-in array method.)
9. Outside of the for loop, use `document.querySelector('.favorites')` to target the div at the bottom of the page. Set the div's `textContent` to `favList.join(' ')`. This will join each of the foods together into a string separated by a space.

View File

@@ -2,7 +2,7 @@
In this demo we'll be creating a simple counter that will display a count and increment on click.
Let's start this demo [in CodePen](https://codepen.io/micahgodbolt/pen/wOWeVb?editors=0010)
Let's start this demo [in CodePen](https://codepen.io/micahgodbolt/pen/wOWeVb?editors=0010).
## React Hello World
@@ -10,15 +10,22 @@ Let's start this demo [in CodePen](https://codepen.io/micahgodbolt/pen/wOWeVb?ed
ReactDOM.render(<p>Hello World</p>, document.getElementById('app'));
```
- `ReactDOM.render()` - This function is how our code gets on the page. The function takes two parameters, the content to place on the page, and the location that you want it placed.
Calling `ReactDOM.render()` is how our code gets on the page. The function takes two parameters: the content to place on the page, and the element in which you want it placed.
The first parameter to `render()` looks a lot like HTML, but actually, it's [JSX](https://reactjs.org/docs/introducing-jsx.html). There are a few key differences between JSX and HTML:
- Since `class` is a [reserved word](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords) in JavaScript, you will need to use `className` on your HTML tags: `<div className="foo">`
- We can use custom HTML tags corresponding to the React components we create: `<div><MyControl>hi</MyControl></div>`
- Controls can be self-closing: `<MyControl text='hi' />`
- You can use JavaScript inside of JSX!
## Writing a React Component
A React component is a piece of code that returns a portion of your application. This can include HTML markup, CSS styles as well as JavaScript driven functionality.
A React component is a piece of code that returns a portion of your application. This can include HTML markup, CSS styles, and JavaScript driven functionality.
Components can be created in two ways. The first is method is to use a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes), which extends (inherits from) the `React.Component` class.
Classes in JavaScript provide a way to collect methods(functions) and properties(values) in an extendable container. We extend `React.Component` because it provides us with several built-in methods, including `render`.
Classes in JavaScript provide a way to collect methods (functions) and properties (values) in an extensible container. We extend `React.Component` because it provides us with several built-in methods, including `render`.
```jsx
class App extends React.Component {
@@ -28,7 +35,7 @@ class App extends React.Component {
}
```
We could also write this component as a function that takes in `props` and returns [JSX](https://reactjs.org/docs/introducing-jsx.html)
We could also write this component as a function:
```jsx
const App = props => {
@@ -36,23 +43,23 @@ const App = props => {
};
```
Moving our "Hello World" markup into our App's `render` function, we can now update the `ReactDOM.render()` function to look like this:
Moving our "Hello World" markup into our App's `render` function, we can now update the `ReactDOM.render()` call to look like this:
```jsx
ReactDOM.render(<App />, document.getElementById('app'));
```
> Note that React components can be reused by writing them in the same way you would an HTML tag
> Note that React components can be reused by writing them in the same way you would an HTML tag.
### Props
Either way you write the component, the component can take in additional props using the same syntax as HTML attribute like `id` or `href`.
Whether you write the component as a class or a function, it can take in additional props using the same syntax as HTML attributes like `id` or `href`.
```jsx
<App text="Hello World" />
```
The prop can then be accessed by your component via `props.text` in our function or `this.props.text` in the class.
The `text` prop can be accessed inside your component via `props.text` in a function component or `this.props.text` in a class component.
```jsx
const App = props => {
@@ -60,7 +67,7 @@ const App = props => {
};
```
`props` allow your component to be more reusable as you can use multiple instances of the same component with different props.
`props` allow your component to be more reusable, since you can create multiple instances of the same component with different props.
```jsx
ReactDOM.render(
@@ -82,7 +89,7 @@ const App = props => {
### Destructuring Props
Writing `props.text` over and over in a function (or `this.props.text` in a class) can be quite tedius. Since this is all JavaScript you could create a new variable for this text using variable assignment.
Writing `props.text` over and over in a function (or `this.props.text` in a class) can be quite tedious. Since this is all JavaScript, you could create a new variable for this text using variable assignment.
```jsx
const App = props => {
@@ -120,9 +127,9 @@ const start = props.config.start;
const end = props.config.end;
```
A common approach to simplify this process is to use a technique called [`destructuring`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring).
A common approach to simplify this process is to use a syntax called [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring).
Destructuring allows you to pull individual pieces of information out of a complex data type in a single line.
Destructuring allows you to pull individual pieces of information out of an object in a single statement.
```jsx
const {
@@ -136,18 +143,11 @@ const {
So while this might be overkill right now, it makes it easier to add props down the road.
```jsx
const App = props => {
const text = props.text;
return <p>{text ? text : 'you missed something'}</p>;
};
```
### Cleanup
Before we move on, we'll reset our `ReactDom.render` to just return our App. This render function typically includes just the single component with no props.
Before we move on, we'll modify our `ReactDOM.render` call to just include our App. This render call typically includes just a single component with no props.
Next we'll be creating a `Counter` component, so we'll add that to our App now, and start to write the control.
Next we'll be creating a `Counter` component. We'll add that to our App now, and then start to write the control.
```jsx
const App = props => {
@@ -167,7 +167,7 @@ React allows each control to specify its own data store, called **state**. We ca
### Adding State
JavaScript classes uses a `constructor` method to instantiate each copy of a class, along with any applicable state. Let's create a new component called `Counter` and give it a state of `clicks` with a default value of `0`;
JavaScript classes use a `constructor` method to instantiate each copy of a class, along with any applicable state. Let's create a new component called `Counter` and give it a state containing a `clicks` property with a default value of `0`;
```js
class Counter extends React.Component {
@@ -181,12 +181,12 @@ class Counter extends React.Component {
```
- The constructor takes in the component's `props`.
- The [`super(props)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super) function calls the constructor of the parent class (in this case `React.Component`).
- The [`super()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super) function calls the constructor of the parent class (in this case `React.Component`).
- Our `counter` state value can now be accessed via `this.state.counter`. Later, we can update state by calling `this.setState({ counter: 1 })`.
### Creating our Counter
For our `Counter` component, the goal is to be able to track how many times the `counter`'s button is clicked. We'll use the following markup.
For our `Counter` component, the goal is to be able to track how many times the counter's button is clicked. We'll use the following markup.
```jsx
render() {
@@ -217,7 +217,9 @@ _onButtonClick = () => {
};
```
> Note that this could also be written as `this.setState(prevState => ({ counter: prevState.counter + 1 }));` to ensure that state is not updated until the previous state has been determined
> This isn't exactly a method, but a class property that is set to an [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). This mostly works the same as `onButtonClick() { }` but eliminates the need for [extra boilerplate](https://medium.freecodecamp.org/this-is-why-we-need-to-bind-event-handlers-in-class-components-in-react-f7ea1a6f93eb) used to avoid potential "gotchas" with [how `this` works in JavaScript](https://codeburst.io/javascript-the-keyword-this-for-beginners-fb5238d99f85).)
> Note that the `setState` call could also be written as `this.setState(prevState => ({ counter: prevState.counter + 1 }));` to ensure that state is not updated until the previous state has been determined.
Now that we have a function to increment our count, all that's left is to connect it to our button.
@@ -231,9 +233,9 @@ Now that we have a function to increment our count, all that's left is to connec
Add a couple `Counter`s to our `App`, each with different text. Notice how they can easy take in different props and maintain their own state.
## Moving this into out codebase
## Moving this into our codebase
To scale our application we are going to need to break up file into smaller, reusable pieces. In this part of the demo we'll look at the `final` folder and how the Javascript module system allows us to break up our components into a collection of files exporting their functionality.
To scale our application we'll need to break up the file into smaller, reusable pieces. In this part of the demo we'll look at the `final` folder and how the JavaScript module system allows us to break up our components into a collection of files exporting their functionality.
### Module Exports and Imports
@@ -241,34 +243,34 @@ Open up the `step1-04/final/components/Counter.tsx` and look at the `Counter` co
```tsx
export class Counter extends React.Component {
...
// ...
}
```
This file exports the Counter component as a 'named' export. This means when we import it we do the following
This file exports the Counter component as a **named export**. This means when we import it we do the following:
```tsx
import { Counter } from './components/Counter';
```
> Note the `{}` wrapped around the import value. This is actually an example of destructuring.
#### Default Exports
We usually export named exports, but it's also possible export a default value like this:
We typically use named exports, but it's also possible export a default value like this:
```tsx
export default class extends React.Component {
...
export default class Counter extends React.Component {
// ...
}
```
In this case the export isn't named, so when we import the component we can call it whatever we want:
When we import the component we can call it whatever we want:
```tsx
import SomeCounterComponent from './components/Counter';
```
> Note that this example doesn't have `{}` wrapped around the import value. This is actually an example of destructuring.
## Using a Button component
Buttons are among the most commonly written components. Custom buttons help abstract common styling, add icons or other decorations, and increase functionality (menu buttons etc). Let's take a quick look at a custom button component to see how it comes together.

View File

@@ -10,7 +10,7 @@ This is our core "business logic" and handles everything our basic "CRUD" operat
Taking a look at our components in `TodoApp`, you can see that our list of props is not just getting longer, but is getting much more complex! We're passing through functions with various signatures, complex `todos` objects, and filter strings which are always one of three values.
As applications grow, it becomes increasing difficult to remember what each function does, or what each todo contains. Also, as JavaScript is a loosely typed language, if I wanted to change the value of `todos` to an array inside my `TodoList`, JavaScript wouldn't care. But if `TodoListItems` was expecting an object, our application would break.
As applications grow, it becomes increasing difficult to remember what each function does, or what each todo contains. Also, as JavaScript is a dynamically typed language, if I wanted to change the value of `todos` to an array inside my `TodoList`, JavaScript wouldn't care. But if `TodoListItems` was expecting an object, our application would break.
It for these two reasons that the entire industry is shifting to writing applications that are strongly typed, and many are using TypeScript to accomplish that.

View File

@@ -59,3 +59,6 @@ async function run() {
}
run();
// Make this file a module so its code doesn't go in the global scope
export { };