-
Notifications
You must be signed in to change notification settings - Fork 19
Update docs and add example app #31
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
|
||
# testing | ||
/coverage | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
.env.local | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
|
||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
.eslintcache |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) Microsoft Corporation. All rights reserved. | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# fluentui-app | ||
|
||
`fluentui-app` is a [Create React App](https://github.com/facebook/create-react-app) that comes with Fluent UI | ||
pre-installed, and serves as an example of how to use `flamegrill` as a standalone tool for performance measurements. | ||
|
||
## How to generate flamegraph for the app | ||
|
||
`fluentui-app` is a standard CRA app with an artificial bottleneck added to it so that it is better visible in the | ||
generated flamegraphs using the `flamegrill` tool. You should note that this example serves as a general example of how | ||
to use `flamegrill` with any CRA app and not specifically FluentUI. | ||
|
||
The bootleneck is called `InefficientComponent`. It is a dummy component that performs a lot pointless work, and is | ||
defined in the `src/App.tsx` as follows | ||
|
||
```tsx | ||
const InefficientComponent: React.FunctionComponent = (props) => { | ||
for (let i = 0; i < 100; i++) { | ||
console.log(i); | ||
} | ||
return <div>{props.children}</div>; | ||
} | ||
``` | ||
|
||
We then wrap the logo image inside of it | ||
|
||
```tsx | ||
export const App: React.FunctionComponent = () => { | ||
return | ||
// ... | ||
<InefficientComponent> | ||
<img src={logo} alt="logo" /> | ||
</InefficientComponent>; | ||
} | ||
``` | ||
|
||
In order to generate the flamegraphs for the app, we need to build the app for production. Normally, we'd simply | ||
run `yarn build` which is an alias for `react-scripts build`, which in turn, under-the-hood calls `webpack` to | ||
optimize and minify the app. However, for the flamegraphs to be readable and meaningful, we need to disable the | ||
minification. While `react-scripts` don't allow us to tweak the `webpack` out of the box, it is possible via | ||
`react-app-rewired` package. With `react-app-rewired` added as a dev dependency, we can tell it to override certain | ||
configuration values of `webpack` such as turning minification off for the production build. You can explore this | ||
further in `config-overrides.js` file. | ||
|
||
If you build the for production using | ||
|
||
``` | ||
node_modules/.bin/react-app-rewired build | ||
``` | ||
|
||
or simply using `yarn build` alias as it's been preconfigured to use `react-app-rewired`. This will put the | ||
production build of the app in the `build` directory. | ||
|
||
Next, create `perf` directory where we'll store generated performance flamegraphs and logs, and run | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be worth adding |
||
`flamegrill` out of with pointing at the generated `build/index.html`. | ||
|
||
``` | ||
mkdir perf && cd perf | ||
../../../node_modules/.bin/flamegrill -n AppTest -s file:///path/to/my/app/build/index.html | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The pathing here is also pretty ugly. Is there a reason this can't be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, we absolutely could. I just wanted to point out that it is fine to run this example using a local build of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's fine, but just for clarity, I think the two most common use cases would be:
I don't think in any case people should have to be diving into a specific |
||
``` | ||
|
||
This will generate flamegraphs in the `perf` directory which can then be previewed using your favourite browser. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
module.exports = { | ||
webpack: function(config, env) { | ||
if (env === 'production') { | ||
config.optimization.minimize = false; | ||
config.output.publicPath = './'; | ||
} | ||
return config; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
{ | ||
"name": "fluentui-app", | ||
"version": "0.1.0", | ||
"private": true, | ||
"dependencies": { | ||
"@fluentui/react": "^7.155.3", | ||
"@testing-library/jest-dom": "^5.11.4", | ||
"@testing-library/react": "^11.1.0", | ||
"@testing-library/user-event": "^12.1.10", | ||
"@types/jest": "^26.0.15", | ||
"@types/node": "^12.0.0", | ||
"@types/react": "^17.0.0", | ||
"@types/react-dom": "^17.0.0", | ||
"react": "^17.0.1", | ||
"react-dom": "^17.0.1", | ||
"react-scripts": "4.0.3", | ||
"typescript": "^4.1.2", | ||
"web-vitals": "^1.0.1" | ||
}, | ||
"scripts": { | ||
"start": "react-scripts start", | ||
"build": "react-app-rewired build", | ||
"test": "react-scripts test", | ||
"eject": "react-scripts eject" | ||
}, | ||
"eslintConfig": { | ||
"extends": [ | ||
"react-app", | ||
"react-app/jest" | ||
] | ||
}, | ||
"browserslist": { | ||
"production": [ | ||
">0.2%", | ||
"not dead", | ||
"not op_mini all" | ||
], | ||
"development": [ | ||
"last 1 chrome version", | ||
"last 1 firefox version", | ||
"last 1 safari version" | ||
] | ||
}, | ||
"devDependencies": { | ||
"react-app-rewired": "^2.1.8", | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
.App { | ||
text-align: center; | ||
} | ||
|
||
.App-logo { | ||
height: 40vmin; | ||
pointer-events: none; | ||
} | ||
|
||
@media (prefers-reduced-motion: no-preference) { | ||
.App-logo { | ||
animation: App-logo-spin infinite 20s linear; | ||
} | ||
} | ||
|
||
.App-header { | ||
background-color: #282c34; | ||
min-height: 100vh; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
font-size: calc(10px + 2vmin); | ||
color: white; | ||
} | ||
|
||
.App-link { | ||
color: #61dafb; | ||
} | ||
|
||
@keyframes App-logo-spin { | ||
from { | ||
transform: rotate(0deg); | ||
} | ||
to { | ||
transform: rotate(360deg); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import React from 'react'; | ||
import { render, screen } from '@testing-library/react'; | ||
import { App } from './App'; | ||
|
||
it('renders "Welcome to Your Fluent UI App"', () => { | ||
render(<App />); | ||
const linkElement = screen.getByText(/Welcome to Your Fluent UI App/i); | ||
expect(linkElement).toBeInTheDocument(); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import React from "react"; | ||
import { Stack, Text, Link, FontWeights, IStackTokens } from "@fluentui/react"; | ||
import logo from "./logo.svg"; | ||
import "./App.css"; | ||
|
||
const boldStyle = { root: { fontWeight: FontWeights.semibold } }; | ||
const stackTokens: IStackTokens = { childrenGap: 15 }; | ||
|
||
const InefficientComponent: React.FunctionComponent = (props) => { | ||
for (let i = 0; i < 100; i++) { | ||
console.log(i); | ||
} | ||
return <div>{props.children}</div>; | ||
}; | ||
|
||
export const App: React.FunctionComponent = () => { | ||
return ( | ||
<Stack | ||
horizontalAlign="center" | ||
verticalAlign="center" | ||
verticalFill | ||
styles={{ | ||
root: { | ||
width: "960px", | ||
margin: "0 auto", | ||
textAlign: "center", | ||
color: "#605e5c", | ||
}, | ||
}} | ||
tokens={stackTokens} | ||
> | ||
<img className="App-logo" src={logo} alt="logo" /> | ||
<Text variant="xxLarge" styles={boldStyle}> | ||
Welcome to Your Fluent UI App | ||
</Text> | ||
<Text variant="large"> | ||
For a guide on how to customize this project, check out the Fluent UI | ||
documentation. | ||
</Text> | ||
<Text variant="large" styles={boldStyle}> | ||
Essential Links | ||
</Text> | ||
<Stack horizontal tokens={stackTokens} horizontalAlign="center"> | ||
<Link href="https://developer.microsoft.com/en-us/fluentui#/get-started/web"> | ||
Docs | ||
</Link> | ||
<Link href="https://stackoverflow.com/questions/tagged/office-ui-fabric"> | ||
Stack Overflow | ||
</Link> | ||
<Link href="https://github.com/microsoft/fluentui/">Github</Link> | ||
<Link href="https://twitter.com/fluentui">Twitter</Link> | ||
</Stack> | ||
<Text variant="large" styles={boldStyle}> | ||
Design System | ||
</Text> | ||
<InefficientComponent> | ||
<img src={logo} alt="logo" /> | ||
</InefficientComponent> | ||
<Stack horizontal tokens={stackTokens} horizontalAlign="center"> | ||
<Link href="https://developer.microsoft.com/en-us/fluentui#/styles/web/icons"> | ||
Icons | ||
</Link> | ||
<Link href="https://developer.microsoft.com/en-us/fluentui#/styles/web"> | ||
Styles | ||
</Link> | ||
<Link href="https://aka.ms/themedesigner">Theme Designer</Link> | ||
</Stack> | ||
</Stack> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
body { | ||
margin: 0; | ||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', | ||
'Droid Sans', 'Helvetica Neue', sans-serif; | ||
-webkit-font-smoothing: antialiased; | ||
-moz-osx-font-smoothing: grayscale; | ||
} | ||
|
||
code { | ||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import { App } from './App'; | ||
import { mergeStyles } from '@fluentui/react'; | ||
import reportWebVitals from './reportWebVitals'; | ||
|
||
// Inject some global styles | ||
mergeStyles({ | ||
':global(body,html,#root)': { | ||
margin: 0, | ||
padding: 0, | ||
height: '100vh', | ||
}, | ||
}); | ||
|
||
ReactDOM.render(<App />, document.getElementById('root')); | ||
|
||
// If you want to start measuring performance in your app, pass a function | ||
// to log results (for example: reportWebVitals(console.log)) | ||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals | ||
reportWebVitals(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/// <reference types="react-scripts" /> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { ReportHandler } from 'web-vitals'; | ||
|
||
const reportWebVitals = (onPerfEntry?: ReportHandler) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's generated by the template, yep. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about source controlling output from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I probably didn't make myself clear last time. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just ran There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait, but that's exactly what is happening here. The only thing we have to rewire is preventing minification which is required to be done regardless of the template used as long as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a disconnect so if this reply doesn't clarify let's chat on the side. :) Is Let me know if this is wrong, but I'm seeing the steps for reproducing this example as:
I'm trying to figure out why it's necessary to add the output from step 1 into git control at all. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, your understanding seems bang on. So my reasoning for including the contents of the |
||
if (onPerfEntry && onPerfEntry instanceof Function) { | ||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { | ||
getCLS(onPerfEntry); | ||
getFID(onPerfEntry); | ||
getFCP(onPerfEntry); | ||
getLCP(onPerfEntry); | ||
getTTFB(onPerfEntry); | ||
}); | ||
} | ||
}; | ||
|
||
export default reportWebVitals; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// jest-dom adds custom jest matchers for asserting on DOM nodes. | ||
// allows you to do things like: | ||
// expect(element).toHaveTextContent(/react/i) | ||
// learn more: https://github.com/testing-library/jest-dom | ||
import '@testing-library/jest-dom'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "es5", | ||
"lib": [ | ||
"dom", | ||
"dom.iterable", | ||
"esnext" | ||
], | ||
"allowJs": true, | ||
"skipLibCheck": true, | ||
"esModuleInterop": true, | ||
"allowSyntheticDefaultImports": true, | ||
"strict": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"noFallthroughCasesInSwitch": true, | ||
"module": "esnext", | ||
"moduleResolution": "node", | ||
"resolveJsonModule": true, | ||
"isolatedModules": true, | ||
"noEmit": true, | ||
"jsx": "react-jsx" | ||
}, | ||
"include": [ | ||
"src" | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is defined as a script entry, why not use
yarn build
?I tried
yarn build
locally and it looks like there's an issue with thepackage.json
:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could indeed. I just wanted to be more explicit about what is happening under-the-hood, but I'm happy to change this one to
yarn build
instead.Ah, excellent catch, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think exposing what's being done here is preferable, and in that case could just be
yarn react-app-rewired build
, which should work even without ascripts
entry. Fundamentally I think you should be leaning on yarn to find your deps for you rather than doing it yourself with a hardcoded path. :)