1675490460
The DevUI Design Design system contains a combination of DevUI rules, Design languages, and best practices. DevUI Design allows developers to focus more on application logic, while designers focus on user experience, interactions, and processes.
To see more in devui.design.
Now supports Angular ^14.0.0
Create a new project
ng new New-Project
Installation:
$ cd New-Project
$ npm i ng-devui
# font icon library
# $ npm i @devui-design/icons
Usage:
import { BrowserModule } from '@angular/platform-browser';
// need for animations
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { DevUIModule } from 'ng-devui';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
DevUIModule
],
bootstrap: [ AppComponent ],
})
export class AppModule { }
Import devui style into angular.json
file:
{
"styles": [
...
"node_modules/ng-devui/devui.min.css"
]
}
Debugging
ng serve --open
Please feel free to contribute code or discuss your idea!
Please make sure you read the contributing guide before making a pull request.
We appreciate all contributors who helped us build DevUI.
Modern browsers and Internet Explorer 11+.
Author: DevCloudFE
Source Code: https://github.com/DevCloudFE/ng-devui
License: MIT license
1675482540
This is my personal blog created with create-react-doc where to write and record some thought daily. It's very thankful to point any wrong place there.
This section we will start tralvel in the world of data strcture together! There is no doubt the travel is so interestring that you'll enjoy it too! Before your travel of data structure, I want to tell two points it's valueble:
algorithm
This section will introduce the world of React progressively. It'll tell the principle of React, how to design React Component gracefully and how to build a simple React from scratch.
More
This section will talk about colorful frontend world.
more
Just do it for fun! You will unlock server, linux, nginx skills from this Raspberry Pi travel.
Thanks for the pi project specially.
Thank my dear rabbit🐰 for her support.
Author: MuYunyun
Source Code: https://github.com/MuYunyun/blog
1673707320
The Hop Protocol v1 monorepo
Install dependencies & link packages
yarn install
yarn run bootstrap
Run frontend app in development
cd packages/frontend
REACT_APP_NETWORK=mainnet yarn run dev
Visit http://localhost:3000/
Library | Current Version | Description |
---|---|---|
@hop-protocol/core | Metadata and config | |
@hop-protocol/sdk | TypeScript Hop SDK |
Application | Current Version | Description |
---|---|---|
@hop-protocol/frontend | React Frontend UI | |
@hop-protocol/hop-node | TypeScript Hop Node |
Author: hop-protocol
Source Code: https://github.com/hop-protocol/hop
License: MIT license
#typescript #javascript #react #npm #sdk #frontend #monorepo
1673489520
"Zero" Api & Type Safe & Fullstack Kit & Powerful Backend
At Alibaba, 2800+ full-stack applications are developed based on Midway Hooks (2022.01)
English | 简体中文
Hooks
for frontend and backendWebpack / Vite
based projectsBackend(Midway Hooks) | Frontend(React) |
---|---|
|
|
Midway Hooks currently provide the following templates:
You can create applications quickly with templates:
npx degit https://github.com/midwayjs/hooks/examples/<name>
For example, create a fullstack application with react:
npx degit https://github.com/midwayjs/hooks/examples/react
git checkout -b my-new-feature
git commit -am 'Add some feature'
git push origin my-new-feature
We use pnpm to manage the project.
$ pnpm install
$ pnpm build
$ pnpm watch
$ pnpm test
Author: Midwayjs
Source Code: https://github.com/midwayjs/hooks
License: MIT license
1672345140
Ansible Configuration Management Database
Ansible-cmdb takes the output of Ansible's fact gathering and converts it into a static HTML overview page (and other things) containing system configuration information.
It supports multiple types of output (html, csv, sql, etc) and extending information gathered by Ansible with custom data. For each host it also shows the groups, host variables, custom variables and machine-local facts.
HTML example output.
(Not all features are supported by all templates)
--template html_fancy
), as seen in the screenshots above.--template html_fancy_split
), with each host's details in a separate file (for large number of hosts).--template csv
), the trustworthy and flexible comma-separated format.--template json
), a dump of all facts in JSON format.--template markdown
), useful for copy-pasting into Wiki's and such.--template markdown_split
), with each host's details in a seperate file (for large number of hosts).--template sql
), for importing host facts into a (My)SQL database.--template txt_table
), for the console gurus.Links to the full documentation can be found below, but here's a rough indication of how Ansible-cmdb works to give you an idea:
Install Ansible-cmdb from source, a release package or through pip: pip install ansible-cmdb
.
Fetch your host's facts through ansible:
$ mkdir out
$ ansible -m setup --tree out/ all
Generate the CMDB HTML with Ansible-cmdb:
$ ansible-cmdb out/ > overview.html
Open overview.html
in your browser.
That's it! Please do read the full documentation on usage, as there are some caveats to how you can use the generated HTML.
All documentation can be viewed at readthedocs.io.
Author: fboender
Source Code: https://github.com/fboender/ansible-cmdb
License: GPL-3.0 license
1672143538
#chromeextension #chrome #extension #ux #uxbook #contentmarketing #design #principles #gooddesign ##ui #userinterface #services #academy #userflow #userjourney #devops #automation #designer #gestalt #ux #designer #skills #interviewquestions #aws #docker#interviewquestions #interview #aws #scenario #cheatsheet #solutionarchitect #azure #ansibleinterview #questions #Devops #interview #guideline #Terraform #cheatsheet #interview #steps #localbusiness #business #videocreating #containor #devops #interview #opportunities #findabestway #certification #top #digitalmarketing #seo #mail #ppc #socialmediamarketing #shorts #technology #frontend #developer #youtube#programming #learn #tech #technology #trending #beginners #worldnews #creative #knowledge #academy #shorts #youtubeshorts #youtube #aws #docker #ui #website #webdesign #development #developer
1672054380
Micro-frontend architectures decompose a front-end app into individual, semi-independent “microapps” working loosely together. This can help make large projects more manageable, e.g. when transitioning from legacy codebases.
Micro-frontend architecture is a design approach in which a front-end app is decomposed into individual, semi-independent “microapps” working loosely together. The micro-frontend concept is vaguely inspired by, and named after, microservices.
The benefits of the micro-frontend pattern include:
Although micro frontends have been getting a lot of attention lately, as of yet there is no single dominant implementation and no clear “best” micro-frontend framework. In fact, there is a variety of approaches depending on the objectives and requirements. See the bibliography for some of the better-known implementations.
In this article, we will skip over much of the theory of micro frontends. Here’s what we won’t cover:
Instead, we will present a micro-frontend tutorial focusing on a concrete implementation, highlighting the important issues in micro-frontend architecture and their possible solutions.
Our implementation is called Yumcha. The literal meaning of “yum cha” in Cantonese is “drinking tea,” but its everyday meaning is “going out for dim sum.” The idea here is that the individual microapps within a macroapp (as we shall call the composed, top-level app) are analogous to the various baskets of bite-size portions brought out at a dim sum lunch.
We will sometimes refer to Yumcha as a “micro-frontend framework.” In today’s world, the term “framework” is usually used to refer to Angular, React, Vue.js, or other similar superstructures for web apps. We are not talking about a framework in that sense at all. We call Yumcha a framework just for the sake of convenience: It is actually more of a set of tools and a few thin layers for building micro frontend–based apps.
Let’s dive in by thinking about how we might define a macroapp and the microapps that make it up. Markup has always been at the heart of the web. Our macroapp will therefore be specified by nothing more complicated than this markup:
<html>
<head>
<script src="/yumcha.js"></script>
</head>
<body>
<h1>Hello, micro-frontend app.</h1>
<!-- HERE ARE THE MICROAPPS! -->
<yumcha-portal name="microapp1" src="https://microapp1.example.com"></yumcha-portal>
<yumcha-portal name="microapp2" src="https://microapp2.example.com"></yumcha-portal>
</body>
</html>
Defining our macroapp using markup gives us full access to the power of HTML and CSS to lay out and manage our microapps. For example, one microapp could sit on top of another one, or to its side, or be up in the corner of the page, or be in one pane of an accordion, or remain hidden until something happens, or stay in the background permanently.
We have named the custom element used for microapps <yumcha-portal>
because “portal” is a promising term for microapps used in the portal proposal, an early attempt at defining a standard HTML element for use in micro frontends.
<yumcha-portal>
Custom ElementHow should we implement <yumcha-portal>
? Since it’s a custom element, as a web component, of course! We can choose from among a number of strong contenders for writing and compiling micro-frontend web components; here we will use LitElement, the latest iteration of the Polymer Project. LitElement supports TypeScript-based syntactic sugar, which handles most of the custom element boilerplate for us. To make <yumcha-portal>
available to our page, we have to include the relevant code as a <script>
, as we did above.
But what does <yumcha-portal>
actually do? A first approximation would be for it to just create an iframe
with the specified source:
render() {
return html`<iframe src=${this.src}></iframe>`;
}
…where render
is the standard LitElement rendering hook, using its html
tagged template literal. This minimal functionality might be almost enough for some trivial use cases.
iframe
siframe
s are the HTML element that everyone loves to hate, but actually they provide extremely useful, rock-solid sandboxing behavior. However, there is still a long laundry list of issues to be aware of when using iframe
s, with potential impact on the behavior and functionality of our app:
iframe
s have well-known quirks in terms of how they size and lay themselves out.iframe
, for better or worse.iframe
will not be reflected in the page’s URL, so we could neither cut and paste URLs to get to the same state of the composed app, nor deep link to them.iframe
from the outside, depending on our CORS setup, might need to go through the postMessage
protocol.iframe
boundaries.iframe
boundary or need the iframe
to have a title they can announce to the user.Some of these issues can be avoided or mitigated by not using iframe
s, an alternative we discuss later in the article.
On the plus side, the iframe
will have its own, independent Content-Security-Policy
(CSP). Also, if the microapp that the iframe
points to uses a service worker or implements server-side rendering, everything will work as expected. We can also specify various sandboxing options to the iframe
to limit its capabilities, such as being able to navigate to the top frame.
Some browsers have shipped or are planning to ship a loading=lazy
attribute for iframe
s, which defers loading below-the-fold iframe
s until the user scrolls near them, but this doesn’t provide the fine-grained control of lazy loading that we want.
The real problem with iframe
s is that the content of the iframe
will take multiple network requests to retrieve. The top-level index.html
is received, its scripts are loaded, and its HTML is parsed—but then the browser must initiate another request for the iframe
’s HTML, wait to receive it, parse and load its scripts, and render the iframe
’s contents. In many cases, the iframe
’s JavaScript would then still have to spin up, make its own API calls, and show meaningful data only after those API calls return and the data is processed for viewing.
This will likely result in undesirable delays and rendering artifacts, especially when several microapps are involved. If the iframe
’s app implements SSR, that will help but still not avoid the necessity for additional round trips.
So one of the key challenges we face in designing our portal implementation is how to deal with this round-trip issue. Our goal is that a single network request should bring down the entire page with all of its microapps, including whatever content each of them is able to prepopulate. The solution to this problem lies in the Yumcha server.
A key element of the micro-frontend solution presented here is to set up a dedicated server to handle microapp composition. This server proxies requests to the servers where each microapp is hosted. Granted, it will require some effort to set up and manage this server. Some micro-frontend approaches (e.g., single-spa) attempt to dispense with the need for such special server setups, in the name of ease of deployment and configuration.
However, the cost of setting up this reverse proxy is more than offset by the benefits we gain; in fact, there are important behaviors of micro frontend–based apps that we simply can’t achieve without it. There are many commercial and free alternatives to setting up such a reverse proxy.
The reverse proxy, in addition to routing microapp requests to the appropriate server, also routes macroapp requests to a macroapp server. That server dishes up the HTML for the composed app in a special way. Upon receiving a request for index.html
from the browser by way of the proxy server at a URL such as http://macroapp.example.com
, it retrieves the index.html
and then subjects it to a simple but crucial transformation before returning it.
Specifically, the HTML is parsed for <yumcha-portal>
tags, which can be done easily with one of the competent HTML parsers available in the Node.js ecosystem. Using the src
attribute to <yumcha-portal>
, the server running the microapp is contacted and its index.html
is retrieved—including server-side rendered content, if any. The result is inserted into the HTML response as a <script>
or <template>
tag, so as not to be executed by the browser.
The advantages of this setup include, first and foremost, that on the very first request for the index.html
for the composed page, the server can retrieve the individual pages from the individual microapp servers in their entirety—including SSR-rendered content, if any—and deliver a single, complete page to the browser, including the content which can be used to populate the iframe
with no additional server round trips (using the underused srcdoc
attribute). The proxy server also ensures that any details of where the microapps are being served from are cloaked from prying eyes. Finally, it simplifies CORS issues, since application requests are all being made to the same origin.
Back at the client, the <yumcha-portal>
tag gets instantiated and finds the content where it was placed in the response document by the server, and at the appropriate time renders the iframe
and assigns the content to its srcdoc
attribute. If we are not using iframe
s (see below), then the content corresponding to that <yumcha-portal>
tag is inserted either into the custom element’s shadow DOM, if we are using that, or directly inline in the document.
At this point, we already have a partially functioning micro frontend–based app.
This is just the tip of the iceberg in terms of interesting functionality for the Yumcha server. For example, we would want to add features to control how HTTP error responses from the microapp servers are handled, or how to deal with microapps that respond very slowly—we don’t want to wait forever to serve the page if one microapp is not responding! These and other topics we will leave for another post.
The Yumcha macroapp index.html
transformation logic could easily be implemented in a serverless lambda-function fashion, or as middleware for server frameworks such as Express or Koa.
Moving back to the client side, there is another aspect to how we implement microapps that is important for efficiency, lazy loading, and jank-free rendering. We could generate the iframe
tag for each microapp, either with a src
attribute—which makes another network request—or with the srcdoc
attribute filled in with the content populated for us by the server. But in both those cases, the code in that iframe
will kick off immediately, including loading all its script and link tags, bootstrapping, and any initial API calls and related data processing—even if the user never even accesses the microapp in question.
Our solution to this problem is to initially represent microapps on the page as tiny inactivated stubs, which can then be activated. Activation can be driven either by the microapp’s region coming into view, using the underused IntersectionObserver
API, or more commonly by pre-notifications sent from the outside. Of course, we can also specify that the microapp be activated immediately.
In any case, when and only when the microapp is activated is the iframe
actually rendered and its code loaded and executed. In terms of our implementation using LitElement, and assuming the activation status is represented by an activated
instance variable, we would have something like:
render() {
if (!this.activated) return html`{this.placeholder}`;
else return html`
<iframe srcdoc="${this.content}" @load="${this.markLoaded}"></iframe>`;
}
Although the microapps making up a macroapp are by definition loosely coupled, they still need to be able to communicate with each other. For example, a navigation microapp would need to send out a notification that some other microapp just selected by the user should be activated, and the app to be activated needs to receive such notifications.
In line with our minimalist mindset, we want to avoid introducing a lot of message-passing machinery. Instead, in the spirit of web components, we will use DOM events. We provide a trivial broadcast API that pre-notifies all stubs of an impending event, waits for any which have requested to be activated for that event type to be activated, and then dispatches the event against the document, on which any microapp can listen for it. Given that all our iframe
s are same-origin, we can reach out from the iframe
to the page and vice-versa to find elements against which to fire events.
In this day and age, we have all come to expect the URL bar in SPAs to represent the application’s view state, so we can cut, paste, mail, text, and link to it to jump directly to a page within the app. In a micro-frontend app, however, the application state is actually a combination of states, one for each microapp. How are we to represent and control this?
The solution is to encode each microapp’s state into a single composite URL and using a small macroapp router which knows how to put that composite URL together and pick it apart. Unfortunately, this requires Yumcha-specific logic in each microapp: to receive messages from the macroapp router and update the microapp’s state, and conversely to advise the macroapp router of changes in that state so the composite URL can be updated. For example, one could imagine a YumchaLocationStrategy
for Angular, or a <YumchaRouter>
element for React.
iframe
CaseAs mentioned above, hosting microapps in iframe
s does have some downsides. There are two alternatives: include them directly inline in the page’s HTML, or place them in the shadow DOM. Both alternatives mirror the pros and cons of iframe
s somewhat, but sometimes in different ways.
For example, individual microapp CSP policies would have to be somehow merged. Assistive technologies such as screen readers should work better than with iframe
s, assuming they support the shadow DOM (which not all do yet). It should be straightforward to arrange to register a microapp’s service workers using the service worker concept of “scope,” although the app would have to ensure that its service worker is registered under the app’s name, not "/"
. None of the layout issues associated with iframe
apply to the inlined or shadow DOM methods.
However, applications built using frameworks such as Angular and React are likely to be unhappy living inline or in the shadow DOM. For those, we are likely going to want to use iframe
s.
The inline and shadow DOM methods differ when it comes to CSS. CSS will be cleanly encapsulated in the shadow DOM. If for some reason we did want to share outside CSS with the shadow DOM, we would have to use constructable stylesheets or something similar. With inlined microapps, all CSS would be shared throughout the page.
In the end, implementing the logic for inline and shadow DOM microapps in <yumcha-portal>
is straightforward. We retrieve the content for a given microapp from where it was inserted into the page by the server logic as an HTML <template>
element, clone it, then append it to what LitElement calls renderRoot
, which is normally the element’s shadow DOM, but can also be set to the element itself (this
) for the inline (non–shadow DOM) case.
But wait! The content served up by the microapp server is an entire HTML page. We cannot insert the HTML page for the microapp, complete with html
, head
, and body
tags, into the middle of the one for the macroapp, can we?
We solve this problem by taking advantage of a quirk of the template
tag in which the microapp content retrieved from the microapp server is wrapped. It turns out that when modern browsers encounter a template
tag, although they do not “execute” it, they do parse it, and in doing so they remove invalid content such as the <html>
, <head>
, and <body>
tags, while preserving their inner content. So the <script>
and <link>
tags in the <head>
, as well as the content of the <body>
, are preserved. This is precisely what we want for purposes of inserting microapp content into our page.
Micro frontends will take root in the webapp ecosystem if (a) they turn out to be a better architectural approach, and (b) we can figure out how to implement them in ways which satisfy the myriad practical requirements of today’s web.
In terms of the first question, no one claims that micro frontends are the right architecture for all use cases. In particular, there would be little reason for greenfield development by a single team to adopt micro frontends. I’ll leave the question of what types of apps in what types of contexts could benefit most from a micro-frontend pattern to other commentators.
In terms of implementation and feasibility, we have seen there are manifold details to be concerned with, including several not even mentioned in this article—notably authentication and security, code duplication, and SEO. Nonetheless, I hope that this article lays out a basic implementation approach for micro frontends which, with further refinement, can stand up to real-world requirements.
Original article source at: https://www.toptal.com/
1672021039
Since some time Visual Studio Code becomes very popular IDE for Javascript developers. I started using it two years ago, and in my opinion, it’s a fantastic code editor. It allows me to customize it just as I want. VS Code also has a build-in git integration and terminal, so you don’t have to jump from one window to another.There are tones of plugins and even themes, where everyone can find something that he or she needs.
The proper setup of VSC can improve our productivity; also, there are some plugins which will help developers to create better, clean code. Because there are so many plugins which can be used for Visual Studio Code, it’s easy to get lost and forget about some useful extensions. This is the reason I would like to share with you ma favorite extensions for VSC for front-end development.
Let’s start!
This is an essential extension for every front-end developer. You don’t have to waste more time for writing every HTML tag manually; it’s enough to put only tag name like div and press enter. Or you can even add a few tags which you would like to be nested like ul>li>a and press enter. What’s important, this extension has all HTML5 snippets.
In the previous point, you could notice that snippets are handy and can help to prevent lots of spelling bugs and can make coding much faster. Each front-end developer works mostly with JavaScript. To speed up my Javascript coding, I use Javascript code snippets. It also supports .ts, .tsx and .jsx files. Here it works similar, for example, to create export default class _ClassName_ {} code it’s enough to put ecl and press tab. Easy, right? To find out more code shortcuts take a look at the extension documentation.
As we have something for HTML and something for Javascript, something for CSS would be useful as well for front-end development. CSS Peek is an extension supporting .html and .js files. It helps to quickly find and check styles applied for selected class or id. It’s beneficial for developers who don’t like to switch between different files or split the screen.
If we are in the code snippets area, it would also be good to mention about extensions for the selected framework. A lot depends on which framework you are using. – For Angular, there is an extension called Angular Snippets (Version 8) because currently, we have Angular 8, but Angular has a new release for every version of the framework. It provides us code snippets for Typescript and HTML.
– For React.js, there is an excellent extension ES7 React/Redux/GraphQL/React-Native snippets. It provides code snippets for React and Redux using ES7, and it works in a similar way to JavaScript snippets with tab button.
– For Vue.js development, there is a great extension called Vetur. It has almost 20mln downloads, and it brings a lot of functionalities like code snippets, linking and errors checking, formatting, debugging or highlighting the syntax. It looks very impressive.
If you want to create a friendly, readable, clean code, it’s a great idea to install ESLint into your VS Code. This will help you to stick to standard practices like indentation, for example.
If we talk about pretty code, it’s worth to install the Prettier extension in your code editor. Prettier is excellent, especially if you are working on the project with other developers. It removes original styling and puts on the consistent code style. Thanks to consistent formatting the code is much more readable.
As I mentioned at the beginning, the Visual Studio Code has a git integration. We can make it even better installing GitLens extension. It allows checking who created each line of code when it was created, and it allows us to go to commit details quickly. It’s beneficial in case of working in a team of developers to understand the code history easily.
Auto import is a great extension which automatically imports files; you don’t have to do it manually anymore. It’s excellent primarily if you work on a component-based project. It’s enough to put the component name, and the plugin will import it.
If we are in the area of imports, there is another great extension which will help you if you need to import something manually or place a link to a different file. Path autocomplete extension provides paths completion. While you start typing your path probably with ./ you will notice a dropdown with folders to select. It’s crazy helpful because you don’t have to dig in your files and search the correct path.
Sometimes you have to remember about adding a new line to your document, and final-newline comes with a helping hand here. Every time you will save the file, it will insert a new line at the end of the document.
Bracket pair colorizer helps us to find the closing bracket of the current block of code. It sometimes happens that at the end of your file or function, you have more than one or two closing brackets, and it’s not so easy to find the correct one then. If you are using the Bracket Pair Colorizer plugin, every starting and closing bracket has the same color for one block of code. So if your opening tag is blue, your closing tag will be blue as well.
Indeticator is an extension for VS Code which visually highlights current intend depth. It allows distinguishing easily different levels of a different block of codes. Depth is marked with small dots and lines.
And at the end an excellent plugin for debugging. Wouldn’t it be perfect if we could debug in the console like in the Chrome browser? It’s possible with Debugger for Chrome plugin; it supports setting breakpoints, stepping, debugging evil scripts, and more. If you are tired of switching from files in the code editor to debugging console in the browser, it’s a great plugin for you.
That’s a great extension that will let you control your js bundle size, and it’s especially helpful when you plan to split your code into small chunks. Some of the libraries you import can weigh a lot, sometimes, one library can be heavier than the whole framework, and you need to be aware of that. Import cost extension will show you the library’s exact size, full-size, and the gzipped one.
Clean code is one of the most important factors that you should care about when building software. And how you manage imports will have a huge impact on your code quality.
Luckily we can use a very helpful extension named Sort Imports, which manages your imports simply and correctly. That extension will sort all of your imports in order and put all of them on the top of the file. What’s better, that extension will group all of your imports as well.
In the above article, I shared with you my favorite extensions for Visual Studio Code. I hope you it’s a great tip on how to set your code editor and improve your performance of your development. Also, if you are a beginner, it may help you to focus on learning programming then on looking for a closing tag, or closing bracket. When your VS Code is configured you are ready to practice Javascript.
Have a nice coding!
Thank you for reading!
What is the difference between Shadow DOM and Virtual DOM?
How to increase your website speed and score 100 on Google PageSpeed Insights
11 React.js projects for beginners, that will help you to build an amazing portfolio and get hired
And if you prefer video, here is the youtube version:
Original article source at: https://www.blog.duomly.com/
1669092120
Cypress is a modern web automation test framework designed to simplify browser testing. While it's best known as a Selenium replacement, it's much more than just an end-to-end test automation tool. Cypress is a developer tool made to be used proactively by developers rather than a non-technical QA team focused on after-the-fact testing.
This post looks at how to introduce Cypress into your test-driven development workflow as you build out an app with Flask and React.
We'll be building a basic todo application with Flask and React based on the following user stories:
Let's assume that our focus is on the client-side only. In other words, we need to create a React todo app that interacts with a Flask back-end via AJAX to get and add todos. If you'd like to code along, clone down the flask-react-cypress repo, and then check out the v1 tag to the master branch:
$ git clone https://github.com/testdrivenio/flask-react-cypress --branch v1 --single-branch
$ cd flask-react-cypress
$ git checkout tags/v1 -b master
This workflow focuses on integration testing, where development and testing happen simultaneously using a TDD-like approach:
Want to see this workflow in action? Check out the My Cypress Workflow video.
Steps:
Add the partial test specs to a new file called client/cypress/integration/todos.spec.js:
describe('todo app', () => {
beforeEach(() => {
cy.visit('/');
cy.get('h1').contains('Todo List');
});
it('should display the todo list', () => {});
it('should add a new todo to the list', () => {});
it('should toggle a todo correctly', () => {});
});
Then, add the baseUrl
along with the serverUrl
--the URL for the server-side Flask app--under an env
key so it will be accessible as an environment variable to client/cypress.json:
{
"baseUrl": "http://localhost:3000",
"env": {
"serverUrl": "http://localhost:5009"
}
}
Before we stub out the requests, let's add the fixture files, which will be used to simulate the data returned from the following server-side endpoints:
client/cypress/fixtures/todos/all_before.json:
{
"data": {
"todos": [
{
"complete": false,
"created_date": "Mon, 28 Jan 2019 15:32:28 GMT",
"id": 1,
"name": "go for a walk"
},
{
"complete": false,
"created_date": "Mon, 28 Jan 2019 15:32:28 GMT",
"id": 2,
"name": "go for a short run"
},
{
"complete": true,
"created_date": "Mon, 28 Jan 2019 15:32:28 GMT",
"id": 3,
"name": "clean the stereo"
}
]
},
"status": "success"
}
client/cypress/fixtures/todos/add.json:
{
"name": "make coffee"
}
This final fixture is for the getting of all todos after a new todo has been added.
client/cypress/fixtures/todos/all_after.json:
{
"data": {
"todos": [
{
"complete": false,
"created_date": "Mon, 28 Jan 2019 15:32:28 GMT",
"id": 1,
"name": "go for a walk"
},
{
"complete": false,
"created_date": "Mon, 28 Jan 2019 15:32:28 GMT",
"id": 2,
"name": "go for a short run"
},
{
"complete": true,
"created_date": "Mon, 28 Jan 2019 15:32:28 GMT",
"id": 3,
"name": "clean the stereo"
},
{
"complete": false,
"created_date": "Mon, 28 Jan 2019 17:22:35 GMT",
"id": 4,
"name": "drink a beverage"
}
]
},
"status": "success"
}
Then, add the fixtures to the beforeEach
in the test spec:
beforeEach(() => {
// fixtures
cy.fixture('todos/all_before.json').as('todosJSON');
cy.fixture('todos/add.json').as('addTodoJSON');
cy.fixture('todos/all_after.json').as('updatedJSON');
cy.visit('/');
cy.get('h1').contains('Todo List');
});
Update the beforeEach
again, adding a stub and an explicit wait:
beforeEach(() => {
// fixtures
cy.fixture('todos/all_before.json').as('todosJSON');
cy.fixture('todos/add.json').as('addTodoJSON');
cy.fixture('todos/all_after.json').as('updatedJSON');
// network stub
cy.server();
cy.route('GET', `${serverUrl}/todos`, '@todosJSON').as('getAllTodos');
cy.visit('/');
cy.wait('@getAllTodos');
cy.get('h1').contains('Todo List');
});
Assign the value of the serverUrl
environment variable to a variable:
const serverUrl = Cypress.env('serverUrl');
Add stubs to the should add a new todo to the list
test:
it('should add a new todo to the list', () => {
// network stubs
cy.server();
cy.route('GET', `${serverUrl}/todos`, '@updatedJSON').as('getAllTodos');
cy.route('POST', `${serverUrl}/todos`, '@addTodoJSON').as('addTodo');
});
Run the React app in one terminal window:
$ cd client
$ npm install
$ npm start
Then, open the Cypress GUI in a different window:
$ cd client
$ ./node_modules/.bin/cypress open
Steps:
Update the test:
it.only('should display the todo list', () => {
cy.get('li').its('length').should('eq', 3);
cy.get('li').eq(0).contains('go for a walk');
});
Then, update the App
component:
import React, { Component } from 'react';
import axios from 'axios';
class App extends Component {
constructor() {
super();
this.state = {
todos: []
};
};
componentDidMount() {
this.getTodos();
};
getTodos() {
axios.get('http://localhost:5009/todos')
.then((res) => { this.setState({ todos: res.data.data.todos }); })
.catch((err) => { });
};
render() {
return (
<div className="App">
<section className="section">
<div className="container">
<div className="columns">
<div className="column is-half">
<h1 className="title is-1">Todo List</h1>
<hr/>
<ul type="1">
{this.state.todos.map(todo =>
<li key={ todo.id } style={{ fontSize: '1.5rem' }}>{ todo.name }</li>
)}
</ul>
</div>
</div>
</div>
</section>
</div>
);
};
};
export default App;
Within componentDidMount
, an AJAX request is sent to the server-side to get all todos. When the response comes back, setState
is called inside the success handler, which re-renders the component, displaying the todo list.
The test should now pass:
Remove the .only
from the passing test, and update the next test:
it.only('should add a new todo to the list', () => {
// network stubs
cy.server();
cy.route('GET', 'http://localhost:5009/todos', '@updatedJSON').as('getAllTodos');
cy.route('POST', 'http://localhost:5009/todos', '@addTodoJSON').as('addTodo');
// asserts
cy.get('.input').type('drink a beverage');
cy.get('.button').contains('Submit').click();
cy.wait('@addTodo');
cy.wait('@getAllTodos');
cy.get('li').its('length').should('eq', 4);
cy.get('li').eq(0).contains('go for a walk');
cy.get('li').eq(3).contains('drink a beverage');
});
Again, update the component:
import React, { Component } from 'react';
import axios from 'axios';
class App extends Component {
constructor() {
super();
this.state = {
todos: [],
input: ''
};
this.handleChange = this.handleChange.bind(this);
this.addTodo = this.addTodo.bind(this);
};
componentDidMount() {
this.getTodos();
};
getTodos() {
axios.get('http://localhost:5009/todos')
.then((res) => { this.setState({ todos: res.data.data.todos }); })
.catch((err) => { });
};
handleChange(e) {
this.setState({ input: e.target.value });
};
addTodo() {
if(this.state.input.length) {
axios.post('http://localhost:5009/todos', { name: this.state.input })
.then((res) => { this.getTodos(); })
.catch((err) => { });
}
};
render() {
return (
<div className="App">
<section className="section">
<div className="container">
<div className="columns">
<div className="column is-half">
<h1 className="title is-1">Todo List</h1>
<hr/>
<div className="content">
<div className="field has-addons">
<div className="control">
<input
className="input"
type="text"
placeholder="Add a todo"
onChange={ this.handleChange }
/>
</div>
<div className="control" onClick={ this.addTodo }>
<button className="button is-info">Submit</button>
</div>
</div>
<ul type="1">
{this.state.todos.map(todo =>
<li key={ todo.id } style={{ fontSize: '1.5rem' }}>{ todo.name }</li>
)}
</ul>
</div>
</div>
</div>
</div>
</section>
</div>
);
};
};
export default App;
Todos can now be added via an input field. The onChange
event is fired, which updates the state, anytime the input value is changed. After the submit button is clicked, an AJAX POST request is sent to the server along with the value from the input field. The todo list is updated when a successful response is returned.
Test:
it.only('should toggle a todo correctly', () => {
cy
.get('li')
.eq(0)
.contains('go for a walk')
.should('have.css', 'text-decoration', 'none solid rgb(74, 74, 74)');
cy.get('li').eq(0).contains('go for a walk').click();
cy
.get('li')
.eq(0).contains('go for a walk')
.should('have.css', 'text-decoration', 'line-through solid rgb(74, 74, 74)');
});
Component:
import React, { Component } from 'react';
import axios from 'axios';
class App extends Component {
constructor() {
super();
this.state = {
todos: [],
input: ''
};
this.handleChange = this.handleChange.bind(this);
this.addTodo = this.addTodo.bind(this);
this.handleClick = this.handleClick.bind(this);
};
componentDidMount() {
this.getTodos();
};
getTodos() {
axios.get('http://localhost:5009/todos')
.then((res) => { this.setState({ todos: res.data.data.todos }); })
.catch((err) => { });
};
handleChange(e) {
this.setState({ input: e.target.value });
};
addTodo() {
if(this.state.input.length) {
axios.post('http://localhost:5009/todos', { name: this.state.input })
.then((res) => { this.getTodos(); })
.catch((err) => { });
}
};
handleClick(id) {
this.setState ({
todos: this.state.todos.map (todo => {
if (todo.id === id) {
todo.complete = !todo.complete;
}
return todo;
}),
});
};
render() {
return (
<div className="App">
<section className="section">
<div className="container">
<div className="columns">
<div className="column is-half">
<h1 className="title is-1">Todo List</h1>
<hr/>
<div className="content">
<div className="field has-addons">
<div className="control">
<input
className="input"
type="text"
placeholder="Add a todo"
onChange={ this.handleChange }
/>
</div>
<div className="control" onClick={ this.addTodo }>
<button className="button is-info">Submit</button>
</div>
</div>
<ul type="1">
{this.state.todos.map(todo =>
<li
key={ todo.id }
style={{
textDecoration: todo.complete ? 'line-through' : 'none',
fontSize: '1.5rem',
}}
onClick={() => this.handleClick(todo.id)}
>
{ todo.name }
</li>
)}
</ul>
</div>
</div>
</div>
</div>
</section>
</div>
);
};
};
export default App;
When a todo li
is clicked, handleClick
is fired, which then toggles the value of the todo.complete
boolean. If the boolean is true
then the todo's text-direction
will be set to line-through
.
Remove the .only
from the final test.
Finally, let's convert the integration tests to end-to-end tests by removing the network stubs and fixtures. We'll do a bit of refactoring as well.
client/cypress/integration/todos-e2e.spec.js:
const serverUrl = Cypress.env('serverUrl');
describe('todo app - e2e', () => {
beforeEach(() => {
// network call
cy.server();
cy.route('GET', `${serverUrl}/todos`).as('getAllTodos');
cy.visit('/');
cy.wait('@getAllTodos');
cy.get('h1').contains('Todo List');
});
it('should display the todo list', () => {
cy.get('li').its('length').should('eq', 2);
cy.get('li').eq(0).contains('walk');
});
it('should toggle a todo correctly', () => {
cy
.get('li')
.eq(0)
.contains('walk')
.should('have.css', 'text-decoration', 'none solid rgb(74, 74, 74)');
cy.get('li').eq(0).contains('walk').click();
cy
.get('li')
.eq(0).contains('walk')
.should('have.css', 'text-decoration', 'line-through solid rgb(74, 74, 74)');
});
});
Before you run these, you will need to spin up the server-side Flask app along with Postgres and create and seed the database:
$ cd server
$ docker-compose up -d --build
$ docker-compose exec web python manage.py recreate_db
$ docker-compose exec web python manage.py seed_db
Now, since the full end-to-end tests require the Flask app to be running, you may not want to run them locally as you're developing. To ignore them, add the ignoreTestFiles
config variable to the cypress.json file:
{
"baseUrl": "http://localhost:3000",
"env": {
"serverUrl": "http://localhost:5009"
},
"ignoreTestFiles": "*e2e*"
}
You can then run them on other environments by using a different configuration file.
Cypress is a powerful tool that makes it easy to set up, write, run, and debug tests. Hopefully, this post showed you how easy it is to incorporate Cypress into your development workflow.
Resources:
Original article source at: https://testdriven.io/
1668609660
In the first tutorial in this series, we built a Single Page Application using Python and Pyodide to load Pandas, fetch a Netflix Dataset, and perform basic computations on the data. We also looked at how Pyodide can be used to manipulate the DOM directly with Python. In the application that we built, we passed processed Netflix data to a JavaScript component and rendered it directly from Python code.
As mentioned in the conclusion to part one, the application is missing some features and we need to address a number of issues. In this second part, we'll:
--
Python Single Page Application Series:
By the end of this tutorial, you should be able to:
First, we'll improve the user experience and application performance by using a web worker. We'll also dive deeper into the Pandas library for analyzing and manipulating the Netflix data in order to create recommendations based on the given titles as well as add random movie and show facts.
You can find a live demo of the application here.
In part one, after loading the Netflix CSV file, we dropped a few unnecessary columns and returned the results as JSON. As you can see, we haven't done much analysis or manipulation of the data yet. We'll look at that now.
If you need the code from part one, you can find it here.
The sanitized DataFrame has the following columns:
Let's create a recommendation list for movies and shows using Pandas. To do so, we'll add a new column to the DataFrame called recommendation_score
, with the value being the weighted sum of imdb_votes
, imdb_score
, tmdb_score
, and tmdb_popularity
:
recommended_titles["recommendation_score"] = (
sanitized_titles["imdb_votes"] * 0.3 +
sanitized_titles["imdb_score"] * 0.3 +
sanitized_titles["tmdb_score"] * 0.2 +
sanitized_titles["tmdb_popularity"] * 0.2
)
Open the index.html file in your code editor of choice, and add the following code after titles_list = sanitized_titles.head(10).to_json(orient="records")
# 4. Create recommendation list for Shows and Movies
# 4.1 Copy the sanitized_titles to add new column to it
recommended_titles = sanitized_titles.copy()
# 4.2 Add new column to the sanitized_titles
recommended_titles["recommendation_score"] = (
sanitized_titles["imdb_votes"] * 0.3 +
sanitized_titles["imdb_score"] * 0.3 +
sanitized_titles["tmdb_score"] * 0.2 +
sanitized_titles["tmdb_popularity"] * 0.2
)
print(recommended_titles.head(5))
Open the file in your browser. Then, within the console in your browser's developer tools, you should see the first five titles. Take note of the recommendation_score
column:
id title ... tmdb_score recommendation_score
tm84618 Taxi Driver ... 8.2 238576.2524
tm127384 Monty Python and the Holy Grail ... 7.8 159270.7632
tm70993 Life of Brian ... 7.8 117733.1610
tm190788 The Exorcist ... 7.7 117605.6374
ts22164 Monty Python's Flying Circus ... 8.3 21875.3838
With that, let's create two new DataFrames, one for movies and another for shows, and then sort them by recommendation_score
in descending order:
recommended_movies = (
recommended_titles.loc[recommended_titles["type"] == "MOVIE"]
.sort_values(by="recommendation_score", ascending=False)
.head(5)
.to_json(orient="records")
)
recommended_shows = (
recommended_titles.loc[recommended_titles["type"] == "SHOW"]
.sort_values(by="recommendation_score", ascending=False)
.head(5)
.to_json(orient="records")
)
Here, we used the loc and sort_values methods to filter the titles by the type
column and sort by recommendation_score
in descending order, respectively.
Replace print(recommended_titles.head(5))
with these new lists:
# 4. Create recommendation list for Shows and Movies
# 4.1 Copy the sanitized_titles to add new column to it
recommended_titles = sanitized_titles.copy()
# 4.2 Add new column to the sanitized_titles
recommended_titles["recommendation_score"] = (
sanitized_titles["imdb_votes"] * 0.3 +
sanitized_titles["imdb_score"] * 0.3 +
sanitized_titles["tmdb_score"] * 0.2 +
sanitized_titles["tmdb_popularity"] * 0.2
)
recommended_movies = (
recommended_titles.loc[recommended_titles["type"] == "MOVIE"]
.sort_values(by="recommendation_score", ascending=False)
.head(5)
.to_json(orient="records")
)
recommended_shows = (
recommended_titles.loc[recommended_titles["type"] == "SHOW"]
.sort_values(by="recommendation_score", ascending=False)
.head(5)
.to_json(orient="records")
)
To use these lists in our application, first we need to add new keys to the App
's state to be able to save and manipulate the data:
state = {
titles: [],
recommendedMovies: [],
recommendedShows: [],
}
Now, to update the state, add the following, right after js.window.appComponent.state.titles = titles_list
:
js.window.appComponent.state.recommendedMovies = recommended_movies
js.window.appComponent.state.recommendedShows = recommended_shows
Finally, to display the recommendations to the end user, add the following to view()
, just below the <!-- End of Titles --!>
line:
<!-- Start of Recommended title --!>
<div class="flex">
<!-- Start of Recommended title --!>
<div class="px-4 sm:px-6 lg:px-8 my-8 w-1/2">
<p class="text-4xl font-semibold text-slate-100">Recommended Movies</p>
<ul role="list" class="divide-y divide-gray-200">
${this.state.recommendedMovies.length > 0 ? JSON.parse(this.state.recommendedMovies).map(function (movie) {
return `
<li class="relative bg-white py-5 px-4 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600 rounded-md my-2">
<div class="flex justify-between space-x-3">
<div class="min-w-0 flex-1">
<p class="text-sm font-semibold text-gray-900 truncate">${movie.title}</p>
<p class="text-sm text-gray-500 truncate">${movie.description}</p>
</div>
<time datetime="" class="flex-shrink-0 whitespace-nowrap text-sm text-gray-500">${movie.release_year}</time>
</div>
</li>
`
}).join('') : `
<li class="relative bg-white py-5 px-4 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600">
<div class="flex justify-between space-x-3">
<div class="min-w-0 flex-1">
<p class="text-sm font-medium text-gray-900 truncate">Loading...</p>
</div>
</div>
</li>
</ul>
`
}
</div>
<!-- End of Recommended titles --!>
<!-- Start of Recommended Shows --!>
<div class="px-4 sm:px-6 lg:px-8 my-8 w-1/2">
<p class="text-4xl font-semibold text-slate-100">Recommended Shows</p>
<ul role="list" class="divide-y divide-gray-200">
${this.state.recommendedShows.length > 0 ? JSON.parse(this.state.recommendedShows).map(function (show) {
return `
<li class="relative bg-white py-5 px-4 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600 rounded-md my-2">
<div class="flex justify-between space-x-3">
<div class="min-w-0 flex-1">
<p class="text-sm font-semibold text-gray-900 truncate">${show.title}</p>
<p class="text-sm text-gray-500 truncate">${show.description}</p>
</div>
<time datetime="" class="flex-shrink-0 whitespace-nowrap text-sm text-gray-500">${show.release_year}</time>
</div>
</li>
`
}).join('') : `
<li class="relative bg-white py-5 px-4 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600">
<div class="flex justify-between space-x-3">
<div class="min-w-0 flex-1">
<p class="text-sm font-medium text-gray-900 truncate">Loading...</p>
</div>
</div>
</li>
</ul>
`}
</div>
<!-- Start of Recommended shows --!>
</div>
<!-- End of Recommended titles --!>
Back in your browser, you should now see the recommended movies and shows.
In this section, we'll find the year that produced the most movies and shows, starting with the Python code:
# 5. Movie and Show Facts
facts_movies = (
sanitized_titles.loc[sanitized_titles["type"] == "MOVIE"]
.groupby("release_year")
.count()["id"]
.sort_values(ascending=False)
.head(1)
.to_json(orient="table")
)
facts_shows = (
sanitized_titles.loc[sanitized_titles["type"] == "SHOW"]
.groupby("release_year")
.count()["id"]
.sort_values(ascending=False)
.head(1)
.to_json(orient="table")
)
Here, we used the:
release_year
.Add the above code to the index.html file, just below the recommendation section.
Update the App
's state again:
state = {
titles: [],
recommendedMovies: [],
recommendedShows: [],
factsMovies: [],
factsShows: [],
}
Update the state:
# 6. set titles to first 10 titles to the state, update remaining state, and render
js.window.appComponent.state.titles = titles_list
js.window.appComponent.state.recommendedMovies = recommended_movies
js.window.appComponent.state.recommendedShows = recommended_shows
js.window.appComponent.state.factsMovies = facts_movies # NEW
js.window.appComponent.state.factsShows = facts_shows # NEW
js.window.appComponent.render()
Update view()
again by adding the following, just after <!-- End of Recommended Shows --!>
:
<!-- Start of Facts --!>
<div class="px-4 sm:px-6 lg:px-8 my-8">
<div>
<h3 class="text-4xl font-semibold text-slate-100">Interesting Facts</h3>
<dl class="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
<div class="px-4 py-5 bg-white shadow rounded-lg overflow-hidden sm:p-6">
${this.state.factsMovies.length > 0 ?
`
<dt class="text-sm font-medium text-gray-500 truncate">Movies produced in ${JSON.parse(this.state.factsMovies).data[0].release_year}</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-900">${JSON.parse(this.state.factsMovies).data[0].id}</dd>
` : `
Loading...
`}
</div>
<div class="px-4 py-5 bg-white shadow rounded-lg overflow-hidden sm:p-6">
${this.state.factsShows.length > 0 ?
`
<dt class="text-sm font-medium text-gray-500 truncate">Shows produced in ${JSON.parse(this.state.factsShows).data[0].release_year}</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-900">${JSON.parse(this.state.factsShows).data[0].id}</dd>
` : `
Loading...
`}
</div>
</dl>
</div>
</div>
<!-- End of Facts --!>
Reload the index.html page in the browser. You should see the interesting facts section with the number of movies and shows produced in the year that produced the most movies and shows.
One of the issues with the current implementation is that we're putting expensive operations in the main thread of the browser. The consequence of this is that other operations will be blocked until Pyodide finishes loading and executing the code. This can have negative impact on the performance of the application and user experience.
To resolve this issue we can use web workers to offload the heavy operations -- Pyodide and the Python script, in this case -- to a separate thread in the background to let the browser's main thread continue running other operations without getting slowed down or locked up.
The main components of a web worker are:
Let's look at a quick example.
Create a new file called worker.js in the root of your project:
self.onmessage = function(message) {
console.log(message.data);
}
This file contains the code the worker will run.
Create a new script
tag in index.html, just before the closing body
tag:
<script>
const worker = new Worker("./worker.js");
worker.postMessage("Hello from the main thread!");
</script>
Due to security reasons, the web worker file cannot be imported from your local file system with the file://
protocol. We'll need to run a local web server to run the project. Within your terminal, navigate to your project's root. Then, run Python's http server:
$ python -m http.server
With the server running, navigate to http://localhost:8000/ in your browser. You should see Hello from the main thread!
in the developer console.
Our goal in this section is to:
First, remove the function definition and call for main()
in index.html. Then, replace all of the code in worker.js with:
// load pyodide.js
importScripts("https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js");
// Initialize pyodide and load Pandas
async function initialize(){
self.pyodide = await loadPyodide();
await self.pyodide.loadPackage("pandas");
}
let initialized = initialize();
Now, add the following code to the worker.js file to run our script when the worker is initialized:
self.onmessage = async function (e) {
await initialized;
response = await fetch(
"https://raw.githubusercontent.com/amirtds/kaggle-netflix-tv-shows-and-movies/main/titles.csv"
);
response.ok && response.status === 200
? (titles = await response.text())
: (titles = "");
// define global variable called titles to make it accessible by Python
self.pyodide.globals.set("titlesCSV", titles);
let titlesList = await self.pyodide.runPythonAsync(`
import pandas as pd
import io
# 1. create csv buffer to make it readable by pandas
csv_buffer = io.StringIO(titlesCSV)
# 2. load the csv file
all_titles = pd.read_csv(csv_buffer)
# 3. sanitize the data
# drop unnecessary columns
all_titles = all_titles.drop(
columns=[
"age_certification",
"seasons",
"imdb_id",
]
)
# drop rows with null values for important columns
sanitized_titles = all_titles.dropna(
subset=[
"id",
"title",
"release_year",
"genres",
"production_countries",
"imdb_score",
"imdb_votes",
"tmdb_score",
"tmdb_popularity",
]
)
# Convert the DataFrame to a JSON object. ('orient="records"' returns a list of objects)
titles_list = sanitized_titles.head(10).to_json(orient="records")
titles_list
`);
let recommendations = await self.pyodide.runPythonAsync(`
# Create recommendation list for Shows and Movies
# 1. Copy the sanitized_titles to add new column to it
recommended_titles = sanitized_titles.copy()
# 2. Add new column to the sanitized_titles
recommended_titles["recommendation_score"] = (
sanitized_titles["imdb_votes"] * 0.3 +
sanitized_titles["imdb_score"] * 0.3 +
sanitized_titles["tmdb_score"] * 0.2 +
sanitized_titles["tmdb_popularity"] * 0.2
)
# 3. Create Recommended movies list
recommended_movies = recommended_titles.loc[recommended_titles["type"] == "MOVIE"].sort_values(
by="recommendation_score", ascending=False
).head(5).to_json(orient="records")
# 4. Create Recommended shows list
recommended_shows = recommended_titles.loc[recommended_titles["type"] == "SHOW"].sort_values(
by="recommendation_score", ascending=False
).head(5).to_json(orient="records")
recommendations = {
"movies": recommended_movies,
"shows": recommended_shows
}
recommendations
`);
let facts = await self.pyodide.runPythonAsync(`
# Create facts list for Movies and Shows
facts_movies = sanitized_titles.loc[sanitized_titles["type"] == "MOVIE"].groupby("release_year").count()["id"].sort_values(ascending=False).head(1).to_json(orient="table")
facts_shows = sanitized_titles.loc[sanitized_titles["type"] == "SHOW"].groupby("release_year").count()["id"].sort_values(ascending=False).head(1).to_json(orient="table")
facts = {
"movies": facts_movies,
"shows": facts_shows
}
facts
`);
self.postMessage({
titles: titlesList,
recommendedMovies: recommendations.toJs({
dict_converter: Object.fromEntries,
}).movies,
recommendedShows: recommendations.toJs({
dict_converter: Object.fromEntries,
}).shows,
factsMovies: facts.toJs({ dict_converter: Object.fromEntries }).movies,
factsShows: facts.toJs({ dict_converter: Object.fromEntries }).shows,
});
};
Here, after analyzing the Netflix data as we did before, we posted the results to the main thread using postMessage
.
Next, in the index.html file after const worker = new Worker("./worker.js");
, add the following code:
worker.postMessage("Running Pyodide");
worker.onmessage = function (event) {
event.data.titles !== undefined ? appComponent.state.titles = event.data.titles : [];
event.data.recommendedMovies !== undefined ? appComponent.state.recommendedMovies = event.data.recommendedMovies : [];
event.data.recommendedShows !== undefined ? appComponent.state.recommendedShows = event.data.recommendedShows : [];
event.data.factsMovies !== undefined ? appComponent.state.factsMovies = event.data.factsMovies : [];
event.data.factsShows !== undefined ? appComponent.state.factsShows = event.data.factsShows : [];
appComponent.render()
}
Stop and restart the Python HTTP server. Refresh the browser.
You should see the same results as before, but with execution of Pyodide and the Python code offloaded to a separate thread.
In this tutorial, we covered how to use Pandas to do data manipulation on our Netflix titles CSV data to create recommendation scores and lists for movies and shows. We also did some data analysis to find in which year most of the movies and shows were produced.
We also improved our application performance by moving Pyodide and the Python code execution to a web worker.
You can find the source code for this tutorial here.
In the next tutorial, we'll add more SPA features to our application, like deleting and editing movies and shows. We'll also add a persistence data layer so that remote data only has be fetched once.
--
Python Single Page Application Series:
Original article source at: https://testdriven.io
1668605820
If you talk to a man in a language he understands, that goes to his head. If you talk to him in his own language, that goes to his heart.
— Nelson Mandela
WebAssembly (WASM) opened the door for many languages to be used in different environments -- like the browser, cloud, serverless, and blockchain, to name a few -- that they couldn't have been used in before. For example, with Pyodide, which leverages WASM, you can run Python in the browser. You can also use runtimes like Wasmer to compile Python to WebAssembly, containerize it, and then run the container in different destinations like edge, IoT, and operating systems.
In this tutorial, you'll build a Single Page Application using Python and Pyodide to manipulate the DOM and manage state.
--
Python Single Page Application Series:
By the end of this tutorial, you should be able to:
What does it mean?
Running Python in the browser means that we can execute Python code directly on the client-side without needing to host and execute code on a server. This is made possible by WebAssembly, which allows us to build true "serverless" applications.
Why is it important? Why not just use JavaScript?
The main goal behind running Python in the browser is not to replace JavaScript but to bring both languages together and let each community use each other's powerful tools and libraries. For example, in this tutorial, we'll be using Python's Pandas library alongside JavaScript.
In this tutorial series, we'll be building a serverless Single Page Application (SPA) that fetches a Netflix Movies and Shows Dataset and uses Pandas to read, sanitize, manipulate and analyze the data. The results are then displayed on the DOM for the end user to see.
Our final project is a SPA that displays a list of movies and television shows, recommended movies and shows, and interesting facts. The end user will be able to delete and filter movies and shows. Data persists in PouchDB.
In this part, we'll focus on:
You can find a live demo of the application that you'll create in this first part here.
Before we dive into building the application, let's quickly look at how we can use Python to interact with the DOM API to manipulate it directly.
To begin with, create a new HTML file called index.html:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script>
</head>
<body>
<p id="title">My first Pyodide app</p>
<script>
async function main() {
let pyodide = await loadPyodide();
pyodide.runPython(`print('Hello world, from the browser!')`);
};
main();
</script>
</body>
</html>
Here, we loaded the main Pyodide runtime along with Pyodide's built-in packages from a CDN and ran a simple Python script using the runPython method.
Open the file in your browser. Then, within the console in your browser's developer tools, you should see:
Loading distutils
Loaded distutils
Python initialization complete
Hello world, from the browser!
The last line shows that our Python code got executed in the browser. Now let's see how we can access the DOM. To do so, we can import the js library to access the JavaScript scope.
Update the HTML file like so:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script>
</head>
<body>
<p id="title">My first Pyodide app</p>
<script>
async function main() {
let pyodide = await loadPyodide();
pyodide.runPython(`
print('Hello world, from the browser!')
import js
js.document.title = "Hello from Python"
`);
};
main();
</script>
</body>
</html>
So, js
represents the global object window
that can then be used to directly manipulate the DOM and access global variables and functions. We used it to change the browser / document title to "Hello from Python".
Refresh the browser to ensure it works.
Next, let's update the paragraph's inner text from "My first Pyodide app" to "Replaced by Python":
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script>
</head>
<body>
<p id="title">My first Pyodide app</p>
<script>
async function main() {
let pyodide = await loadPyodide();
pyodide.runPython(`
print('Hello world, from the browser!')
import js
js.document.title = "Hello from Python"
js.document.getElementById("title").innerText = "Replaced by Python"
`);
};
main();
</script>
</body>
</html>
Save the file, and refresh the page again.
One great feature of Pyodide is that you can pass objects between Python and JavaScript. There are two translation methods:
str
to JavaScript String
.As mentioned, basic data types will be converted directly between Python and JavaScript without a need to create special objects.
Python | JavaScript |
---|---|
int | Number or BigInt |
float | Number |
str | String |
bool | Boolean |
None | undefined |
JavaScript | Python |
---|---|
Number | int or float |
BigInt | int |
String | str |
Boolean | bool |
undefined | None |
null | None |
Let's look at an example.
Start by adding a new variable called name
to the script
tag:
<script>
var name = "John Doe"; // NEW!
async function main() {
let pyodide = await loadPyodide();
pyodide.runPython(`
print('Hello world, from the browser!')
import js
js.document.title = "Hello from Python"
js.document.getElementById("title").innerText = "Replaced by Python"
`);
};
main();
</script>
Next, let's take a look at the type and value using js:
<script>
var name = "John Doe";
async function main() {
let pyodide = await loadPyodide();
pyodide.runPython(`
print('Hello world, from the browser!')
import js
js.document.title = "Hello from Python"
js.document.getElementById("title").innerText = "Replaced by Python"
`);
// NEW !!
pyodide.runPython(`
import js
name = js.name
print(name, type(name))
`);
};
main();
</script>
Refresh the browser. Within the console, you should see the following output:
John Doe <class 'str'>
As you can see, we have direct access to this variable's value, and it got converted from a JavaScript String
to Python str
.
As you've seen, basic types can be converted directly to their equivalent in the target language. On the other hand, "non-basic" types need to be converted to a proxy object. There are two types of proxy objects:
To convert a Python dictionary to JavaScript object, use the
dict_converter
argument with a value ofObject.fromEntries
:dictionary_name.toJs({ dict_converter: Object.fromEntries })
Without this argument,
toJs()
will convert the dictionary to a JavaScript Map object.
Create a new variable called products
:
var products = [{
id: 1,
name: "product 1",
price: 100,
}, {
id: 2,
name: "Product 2",
price: 300,
}];
Import it into runPython
and check the type:
pyodide.runPython(`
import js
print(type(js.products))
`);
Full script
tag:
<script>
var name = "John Doe";
// NEW !!
var products = [{
id: 1,
name: "product 1",
price: 100,
}, {
id: 2,
name: "Product 2",
price: 300,
}];
async function main() {
let pyodide = await loadPyodide();
pyodide.runPython(`
print('Hello world, from the browser!')
import js
js.document.title = "Hello from Python"
js.document.getElementById("title").innerText = "Replaced by Python"
`);
pyodide.runPython(`
import js
name = js.name
print(name, type(name))
`);
// NEW !!
pyodide.runPython(`
import js
print(type(js.products))
`);
};
main();
</script>
After refreshing the page, you should see in the console that the result is <class 'pyodide.JsProxy'>
. With this proxy, we have access to the JavaScript object in memory from our Python code.
Update the newly added pyodide.runPython
block like so:
pyodide.runPython(`
import js
products = js.products
products.append({
"id": 3,
"name": "Product 3",
"price": 400,
})
for p in products:
print(p)
`);
You should see an AttributeError: append
error in the browser since the JSProxy object doesn't have an append
method.
What happens if you change
.append
to.push
?
To manipulate this object, you can convert it to a Python object using the to_py()
method:
pyodide.runPython(`
import js
products = js.products.to_py()
products.append({
"id": 3,
"name": "Product 3",
"price": 400,
})
for p in products:
print(p)
`);
You should now see:
{'id': 1, 'name': 'product 1', 'price': 100}
{'id': 2, 'name': 'Product 2', 'price': 300}
{'id': 3, 'name': 'Product 3', 'price': 400}
Update the script
tag like so:
<script>
async function main() {
let pyodide = await loadPyodide();
pyodide.runPython(`
import js
products = [
{
"id": 1,
"name": "new name",
"price": 100,
"votes": 2
},
{
"id": 2,
"name": "new name",
"price": 300,
"votes": 2
}
]
`);
let products = pyodide.globals.get("products");
console.log(products.toJs({ dict_converter: Object.fromEntries }));
};
main();
</script>
Here, we accessed the Python variable and then converted it from a Python dict to a JavaScript object via .toJs({ dict_converter: Object.fromEntries })
.
After refreshing the page, you should see the following output in the console:
{id: 1, name: 'new name', price: 100, votes: 2}
{id: 2, name: 'new name', price: 300, votes: 2}
With that, let's put your newfound Pyodide knowledge to use and build an application!
We'll be developing a serverless SPA application that fetches a Netflix dataset. We'll then use Pandas to read, sanitize, manipulate, and analyze the data. Finally, we'll pass the results to the DOM to display the analyzed data to the end user.
The dataset is a CSV, which includes the following columns:
Name | Description |
---|---|
ID | The title ID on JustWatch. |
title | The name of the title. |
show type | TV show or movie. |
description | A brief description. |
release year | The release year. |
age certification | The age certification. |
runtime | The length of the episode (show) or movie. |
genres | A list of genres. |
production countries | A list of countries that produced the title. |
seasons | Number of seasons if it's a show. |
IMDB ID | The title ID on IMDB. |
IMDB Score | Score on IMDB. |
IMDB Votes | Votes on IMDB. |
TMDB Popularity | Popularity on TMDB. |
TMDB Score | Score on TMDB. |
Update the content of your index.html file like so:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-900">
<div id="app" class="relative h-full max-w-7xl mx-auto my-16"></div>
<script>
</script>
</body>
</html>
As you can see, we loaded Pyodide from a CDN along with Tailwind CSS for styling. We also defined a <div>
element with an id
of app
to hold the App component we'll be building out next.
Add the following JavaScript code to the script
tag:
class App {
state = {
titles:[],
}
view() {
return `<p class="text-slate-100">Hello, World!</p>`
}
render() {
app.innerHTML = this.view();
}
}
Here, we defined an object called App
. We'll refer to this as a component since it's an independent, reusable piece of code.
The App
component has a state object for holding data along with two nested functions called view()
and render()
. render()
simply appends the outputted HTML code from view()
to the DOM, to the div
with an id
of app
.
Let's create a new instance of App
called appComponent
and call render()
on it. Add the following code after the class
declaration of App
:
var appComponent = new App();
appComponent.render();
Open the file in your browser. You should see "Hello, World!".
Next, let's add sample movies to the state
. In the script
tag, right before calling appComponent.render();
, update the state with the following:
appComponent.state.titles = [
{
"id": 1,
"title": "The Shawshank Redemption",
"release_year": 1994,
"type": "MOVIE",
"genres": [
"Crime",
"Drama"
],
"production_countries": [
"USA"
],
"imdb_score": 9.3,
"imdb_votes": 93650,
"tmdb_score": 9.3,
},
{
"id": 2,
"title": "The Godfather",
"release_year": 1972,
"type": "MOVIE",
"genres": [
"Crime",
"Drama"
],
"production_countries": [
"USA"
],
"imdb_score": 9.2,
"imdb_votes": 93650,
"tmdb_score": 9.3,
}
];
Now, we can construct a table to display the data by updating view()
in our App
class like so:
view() {
return (`
<div class="px-4 sm:px-6 lg:px-8">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-4xl font-semibold text-gray-200">Netflix Movies and Shows</h1>
</div>
</div>
<!-- Start of Titles --!>
<div class="mt-8 flex flex-col">
<div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
<table class="min-w-full divide-y divide-gray-300">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="whitespace-nowrap py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">Title</th>
<th scope="col" class="whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900">Type</th>
<th scope="col" class="whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900">Release Year</th>
<th scope="col" class="whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900">Genre</th>
<th scope="col" class="whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900">Production Country</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
${this.state.titles.length > 0 ? this.state.titles.map(function (title) {
return (`
<tr id=${title.id}>
<td class="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-500 sm:pl-6">${title.title}</td>
<td class="whitespace-nowrap px-2 py-2 text-sm font-medium text-gray-900">${title.type}</td>
<td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">${title.release_year}</td>
<td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">${title.genres}</td>
<td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">${title.production_countries}</td>
</tr>
`)
}).join('') : (`
<tr>
<td class="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-500 sm:pl-6">Titles are loading...</td>
<td class="whitespace-nowrap px-2 py-2 text-sm font-medium text-gray-900">Titles are loading...</td>
<td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">Titles are loading...</td>
<td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">Titles are loading...</td>
<td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">Titles are loading...</td>
</tr>
`)
}
</tbody>
</table>
<div>
</div>
</div>
</div>
<!-- End of Titles --!>
</div>
`)
}
So, we-
state.titles
array length to see if it contains any titles. If there are titles, we looped through them and created a table row for each. If not, we created a table row with a loading message.Refresh the page in the browser.
To load Python packages in Pyodide, you can use the loadPackage function right after initializing Pyodide.
For example:
let pyodide = await loadPyodide();
await pyodide.loadPackage("requests");
You can load multiple packages using a list:
await pyodide.loadPackage(["requests", "pandas", "numpy"]);
Back in your HTML file, add a main
function after appComponent.render();
:
async function main() {
let pyodide = await loadPyodide();
await pyodide.loadPackage("pandas");
}
Don't forget to call it:
main();
Refresh the page in your browser. You should see the following in the console:
Loading pandas, numpy, python-dateutil, six, pytz, setuptools, pyparsing
Loaded python-dateutil, six, pytz, pyparsing, setuptools, numpy, pandas
So, Pandas and the relevant sub-dependencies were loaded into the browser!
In this section, we'll fetch a CSV file from the internet, read it into a Pandas DataFrame, sanitize and manipulate it, and finally pass it to the state.
Python code:
import js
import pandas as pd
from pyodide.http import pyfetch
# 1. fetching CSV from and write it to memory
response = await pyfetch("https://raw.githubusercontent.com/amirtds/kaggle-netflix-tv-shows-and-movies/main/titles.csv")
if response.status == 200:
with open("titles.csv", "wb") as f:
f.write(await response.bytes())
# 2. load the csv file
all_titles = pd.read_csv("titles.csv")
# 3. sanitize the data
# drop unnecessary columns
all_titles = all_titles.drop(
columns=[
"age_certification",
"seasons",
"imdb_id",
]
)
# drop rows with null values for important columns
sanitized_titles = all_titles.dropna(
subset=[
"id",
"title",
"release_year",
"genres",
"production_countries",
"imdb_score",
"imdb_votes",
"tmdb_score",
"tmdb_popularity",
]
)
# Convert the DataFrame to a JSON object. ('orient="records"' returns a list of objects)
titles_list = sanitized_titles.head(10).to_json(orient="records")
# 4. set titles to first 10 titles to the state
js.window.appComponent.state.titles = titles_list
js.window.appComponent.render()
Take note of the code comments.
Add this code to a runPythonAsync method within main
:
async function main() {
let pyodide = await loadPyodide();
await pyodide.loadPackage("pandas");
await pyodide.runPythonAsync(`
// add the code here
`);
}
Next, remove appComponent.state.titles
. Also, we need to change this line in the view
method:
${this.state.titles.length > 0 ? this.state.titles.map(function (title) {
To:
${this.state.titles.length > 0 ? JSON.parse(this.state.titles).map(function (title) {
Why?
titles_list
(titles_list = sanitized_titles.head(10).to_json(orient="records")
) is a JSON string, so in order to iterate over it, we need to deserialize it.
Refresh the page in your browser. You should first see a loading message in the table. After Pyodide loads, Pandas imports, and, after the script finishes executing, you should see the full movies list.
We covered a lot in this tutorial! We looked at how Pyodide can let you run Python code in the browser, giving you the power to:
You can find the source code for this tutorial here.
--
We're still missing a few things and we need to address a few issues, though:
We'll cover these four things in the next tutorials!
--
Python Single Page Application Series:
Original article source at: https://testdriven.io
1667651716
#react #daisyui #frontend #frontenddev
https://reacthustle.com/blog/how-to-implement-a-react-autocomplete-input-using-daisyui
1667422080
An enterprise-class Angular UI component library based on Ant Design.
English | 简体中文
^14.0.0
![]() IE / Edge | ![]() Firefox | ![]() Chrome | ![]() Safari | ![]() Opera | ![]() Electron |
---|---|---|---|---|---|
Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
ng-zorro-antd
synchronizes design specification with Ant Design on a regular basis, you can check the log online.
We recommend using @angular/cli
to install. It not only makes development easier, but also allows you to take advantage of the rich ecosystem of angular packages and tooling.
$ ng new PROJECT_NAME
$ cd PROJECT_NAME
$ ng add ng-zorro-antd
More information about
@angular/cli
here.
You can also install ng-zorro-antd
with npm or yarn
$ npm install ng-zorro-antd
Import the component modules you want to use into your app.module.ts
file and feature modules.
import { NzButtonModule } from 'ng-zorro-antd/button';
@NgModule({
imports: [ NzButtonModule ]
})
export class AppModule {
}
@angular/cli
users won't have to worry about the things below but it's good to know.
And import style and SVG icon assets file link in angular.json
.
{
"assets": [
+ {
+ "glob": "**/*",
+ "input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
+ "output": "/assets/"
+ }
],
"styles": [
+ "node_modules/ng-zorro-antd/ng-zorro-antd.min.css"
]
}
See Getting Started for more details.
$ git clone git@github.com:NG-ZORRO/ng-zorro-antd.git
$ cd ng-zorro-antd
$ npm install
$ npm run start
Browser would open automatically.
We welcome all contributions. Please read our CONTRIBUTING.md first. You can submit any ideas as pull requests or as GitHub issues.
If you're new to posting issues, we ask that you read How To Ask Questions The Smart Way (This guide does not provide actual support services for this project!), How to Ask a Question in Open Source Community and How to Report Bugs Effectively prior to posting. Well written bug reports help us help you!
Thanks to JetBrains for supporting us free open source licenses.
For questions on how to use ng-zorro-antd, please post questions to using the
ng-zorro-antd
tag. If you're not finding what you need on stackoverflow, you can find us on as well.
As always, we encourage experienced users to help those who are not familiar with ng-zorro-antd
!
We list some users here, if your company or product uses NG-ZORRO, let us know here!
Author: NG-ZORRO
Source Code: https://github.com/NG-ZORRO/ng-zorro-antd
License: MIT license
1666334936
In this tutorial, you'll learn how to build a Lottery App with Solidity (Infura) and Next.js. This is the BEST tutorial to learn the basics of Solidity Development!
🔗 GitHub Repo: https://github.com/CleverProgrammers/lottery-dapp-youtube
#web3 #frontend #blockchain #nextjs #solidity
1665506546
https://www.youtube.com/watch?v=dXy4qXEk_lA&t=926s
Like & Subscribe:
https://www.youtube.com/thecodeangle
For more resources on Web Development:
https://thecodeangle.com/
Follow Me on Twitter:
https://twitter.com/thecodeangle
#React #firebase #fullstack #web-development #bootstrap #frontend