BlazingPizza: aplicativo Blazor do início ao fim. Parte 1. Configurando o Ambiente

Olá a todos! Se você já ouviu falar de Blazor , mas ainda não entende o que é. Então você está no endereço. Este é o primeiro artigo de uma série de 12 artigos que leva você por todos os círculos do inferno durante todo o processo de criação de um aplicativo no Blazor . No final, teremos um site pronto para uma pequena pizzaria, bem no nível de sites prontos para algumas pizzarias. Portanto, você terá orgulho de;)

Isso será um pouco fora do comum do HelloWorld . Eu deliberadamente resolvi algumas complicações que devem ser tomadas imediatamente como regras; em particular, essa é uma arquitetura típica de três camadas: View-Domain-DataAccess .

Acredito que é melhor segui-lo imediatamente do que mostrar algo artificial, não relacionado à vida. Como mostra a prática, muitos desenvolvedores ficam paralisados ​​no nível HelloWorld e depois usam o que viram ao projetar grandes aplicativos a priori (olá ao meu ex-banco mais verde do país e seu Service.cs com todo o código do aplicativo em um arquivo, não estou brincando).

Vou lhe dizer como mapear dados entre camadas sem problemas e sem a necessidade de escrever muitas linhas de código. Também implantaremos tudo no Azure DevOps. Sim, agora essa é uma parte necessária de qualquer processo de desenvolvimento, todos nós voamos nas nuvens hoje, algumas no local , outras no Azure ou na AWS .

Além disso, para que o primeiro artigo não fosse uma história banal sobre como pressionar um botão no Visual Studio e depois outro, pensei que seria bom organizar um pequeno desafio e tentar fazer algo no Linux pela primeira vez .

Espero que você tenha um conhecimento mínimo do ASP.NET, C #. Embora seja o HelloWorld, não vou falar sobre o que é o Program.cs.

A seguir, discutiremos o que mais é necessário para a programação no Linux .
Atenção: Nos parágrafos a seguir, minha impressão é recebida de um trabalho real com o Linux, que o instalou da melhor das intenções, se o Linux lhe dá sentimentos entusiasmados e você não pode viver um único dia sem abrir a linha de comando, pode arruinar seu humor, Continuando lendo este artigo!

Distribuição: Ubuntu


Na verdade, você não se sentirá à vontade para programar no Linux, mas se quiser se concentrar nos negócios, instale o Ubuntu 19.10 . Consegui colocar na segunda vez, é bom que pelo menos na segunda vez. E todas as equipes completaram a primeira vez, quase todas. Aconselho fortemente a não instalar outras distribuições, passei o dia inteiro configurando o OpenSuse mais recente e depois o demoli.

Por que você ainda precisa desenvolver no Linux ? Bem, pelo menos porque o .Net Core o suporta e talvez seu empregador decida economizar dinheiro e executar tudo no Linux no produto. E, na minha opinião, escrever é melhor no SO em que seu aplicativo realmente será executado.

Vencedor do IDE: Rider


Como eu disse, ainda não é possível escrever confortavelmente no Linux ; para todos os males, vamos escolher o menor, Rider.

Além disso, também existem dois IDEs mais populares , como os editores de texto MonoDevelop e Visual Studio Code . Vou falar sobre as desvantagens de cada solução em mais detalhes.

Monodesenvolvimento


O mais fofo dos três editores. Fontes legíveis (fontes de baixa qualidade são a primeira coisa que chamam sua atenção ao mudar para o Linux). Infelizmente, por trás do belo shell, há um vazio, apesar do .Net Core 2.1 e 3.1 instalado , o MonoDevelop persistentemente criou o ConsoleApplication com o destino .Net Core 1.1. Blazor não conseguiu criar o projeto, nem iniciar o criado manualmente.

Código do Visual Studio


Estritamente falando, esse não é um IDE , é um editor de texto com destaque de sintaxe e capacidade de criação. Das vantagens da terceira ou quarta tentativa, o VS Code lançou milagrosamente o projeto WebAssembly que eu criei quando o MonoDevelop e o Rider se recusaram a fazer isso. Isto é uma vantagem. Listo ainda mais os pontos negativos: uma fonte padrão pequena e ilegível, acho que depois de alguns meses de trabalho regular, seus olhos simplesmente enlouquecem.

O próximo ponto negativo vem do fato de que este é um editor de texto, uma ação tão simples como renomear, mover projetos? Tudo o que o Visual Studio faz automaticamente, e tomamos como garantido, é feito manualmente aqui.

Cavaleiro


Tudo é muito melhor aqui do que os camaradas acima, Rider até pegou o modelo que eu instalei para o Blazor WebAssembly . E ele até criou um aplicativo, mas por algum motivo esse IDE se recusou a iniciá-lo. Por uma questão de justiça, o Blazor Server foi montado e iniciado sem problemas. O Rider

também permite adicionar um repositório ao criar um projeto git. Mas, ao mesmo tempo, o .gitignore de alguma forma acabou vazio. Isto é, novamente, é necessária intervenção manual, e o Rider, caso eu lembre, concorra com o VS e o JetBrains

leva dinheiro para o seu produto. Bem, e como sempre, o problema, em geral no Linux, em princípio, são fontes ruins, são muito finas e pouco legíveis. Os elementos também são todos pequenos, ainda precisam ser direcionados. Além disso, várias vezes, em dois dias de trabalho, o Rider suspendeu o sistema ao carregar uma solução simples. O computador teve que ser reiniciado com uma redefinição, o que obviamente não é bom (atualização: quando este artigo foi escrito, isso já havia acontecido cinco vezes).

Vencedor na nomeação Melhor C # IDE para Linux :
Rider - apesar de todas as deficiências, consegui garantir que esse IDE montasse e iniciasse o projeto de forma consistente, novamente, por algum motivo, o navegador não foi iniciado por padrão, isso deve ser feito manualmente.

Abaixo na captura de tela, Rider (preste atenção às fontes) e uma tentativa de executar o projeto Blazor WebAssembly , criado por ele mesmo, com o MonoDevelop em uma situação semelhante.



Se tudo está tão ruim, por que usar o Linux ?


Deixe-me lembrá-lo de que este é um projeto de treinamento. O objetivo era mostrar que você ainda pode trabalhar no Linux.

Além disso, a imperfeição das ferramentas de desenvolvimento do Linux nos leva a várias ações manuais que nos permitem aprender um pouco mais sobre como o .Net funciona sob o capô e entender quanto trabalho o Visual Studio faz para nós.

O que queremos obter?


No final, obtemos um bom aplicativo, como no exemplo abaixo:



E agora para o caso. Vamos começar instalando o .Net Core 3.1

Instale o .Net Core 3.1


O .Net Core 3.1.1 SDK é a versão mais recente disponível do SDK , está disponível no repositório Ubuntu . O Blazor muda muito rapidamente, portanto, para trabalhar com o Blazor WebAssembly, use 3.1.1, a versão estável mais recente.

No começo, precisamos registrar a chave da Microsoft e seu repositório, isso é feito com os seguintes comandos no aplicativo Terminal (clique no botão Win e digite Term , ele deve aparecer na lista de disponíveis):



  1. wget -q https://packages.microsoft.com/config/ubuntu/19.10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
  2. sudo dpkg -i packages-microsoft-prod.deb

Agora vá diretamente para a instalação do .Net Core SDK:

  1. sudo apt-get update
  2. sudo apt-get install apt-transport-https
  3. sudo apt-get update
  4. sudo apt-get install dotnet-sdk-3.1

Instale o modelo do Blazor WebAssembly


Com a instalação do .Net Core concluída, agora você precisa instalar o Blazor WebAssembly Template , que eu lembrarei enquanto estiver no estágio Preview , do qual será lançado imediatamente em maio. Um dia antes de começar a escrever o artigo, a versão atualizada 3.2.0-preview1.20073.1 (h) foi lançada em 28 de janeiro

dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.2.0-preview1.20073.1

Instalação Git


Se você ainda não instalou o Git , é hora de fazê-lo. Estritamente falando, o primeiro comando não é necessário, o Ubuntu solicitará que você digite uma senha; se forem necessários privilégios para executar o comando, anteciparemos isso =):

  1. sudo su
  2. add-apt-repository ppa:git-core/ppa
  3. apt update; apt install git

Isso não é tudo, o Blazor usa o Mono quando executado no navegador , o que significa que precisamos instalá-lo também.

Instalação Mono


Primeiro, adicione os repositórios Mono ao sistema:

  1. sudo apt install gnupg ca-certificates
  2. sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
  3. echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list
  4. sudo apt update

Agora instale diretamente o próprio Mono :

sudo apt install mono-complete

Este é um processo rápido que levou cerca de 10 minutos no meu laptop com um processador Core-i5 6200U, porque a compilação da fonte ocorre no seu computador.

Parabéns! Instalamos tudo o que você precisa para desenvolver projetos Blazor WebAssembly.
Agora você pode ir diretamente para o próprio projeto.

Mas antes disso, interrompa a digitação de comandos e atualize na memória o que é o Blazor WebAssembly.

Blazor WebAssembly e com que é consumido


O Blazor é uma estrutura da Web projetada para ser executada no lado do cliente no navegador via tecnologia WebAssembly (usando o tempo de execução .Net , ou melhor , implementações Mono , que recentemente, graças à abertura de fontes .Net, quase não são diferentes da plataforma original), ou no lado do servidor com hospedagem no aplicativo Asp.Net Core e troca de dados através da tecnologia SignalR (através da criação de conexões de circuitos ).

Como você pode ver na descrição acima, implementação do Blazor! = .Net WebAssembly.
Se você tirar o WebAssembly deste pacote , poderá desenvolver um aplicativo Blazor Server e ele funcionará mesmo no IE 11quem nunca ouviu nada sobre a tecnologia moderna e, graças a Deus, nunca mais ouvirá.

Como estamos criando um aplicativo com base na abordagem Blazor WebAssembly . Então vamos considerar um pouco mais.

  1. index.html app, . blazor.webassembly.js . Blazor , ( AutoMapper, AutoFac) Mono .

    Blazor , .

    Blazor(3.1.0-preview4.19579.2, ) .Net, Blazor, .

    Blazor , , , .
  2. Talvez a principal desvantagem dessa abordagem seja a depuração complicada, você não pode definir pontos de interrupção, você simplesmente não se enquadra neles, embora eu ache que isso seja temporário, porque foi possível conectar-se ao IE e depurar o código JS anteriormente no estúdio .

    Bem, talvez o segundo menos óbvio seja a necessidade de baixar todas as fontes, mais uma vez acho que ainda existem muitas oportunidades de otimização.
  3. Na verdade, após o download e todos os processos preparatórios, nosso aplicativo é executado no fluxo da interface do usuário do navegador e inicia sua execução. Você pode chamar qualquer código JS de C # e vice-versa.

Não há fotos há muito tempo, então aqui está uma abaixo :)



Utilitário Dotnet


Além disso, usaremos ativamente o utilitário do .Net Core SDK - dotnet.

Ele permite que você faça muitas coisas úteis, estamos interessados ​​apenas em algumas:

dotnet build 

O comando acima coleta a solução. Se você iniciar da pasta com a solução, nenhum argumento será necessário.

dotnet new blazorwasm -ho -o BlazingPizza

Ele permite que você crie um novo projeto com base no modelo que vem após a palavra new; no nosso caso, é blazorwasm ; aqui, por exemplo, você pode especificar classlib ou console .

A seguir, estão os parâmetros específicos do modelo, neste caso -ho , esse parâmetro indica que nosso aplicativo será hospedado no servidor no aplicativo ASP.NET Core , e também o .Net criará controladores para nós, que podemos usar em particular para criar API da Web e acesse-a não apenas a partir do aplicativo Blazor, mas também de outros clientes.

A última opção -o indica a pasta na qual a solução criada será colocada, por padrão, este é o diretório atual.

dotnet run 

Outra equipe que é útil, como o nome indica, inicia nosso aplicativo.

Criação de projeto


E assim, executaremos a equipe familiar para gerar nossa solução. Para isso, selecione um local que você conhece, por exemplo, a pasta RiderProjects que o Rider IDE criou e que está localizada no caminho / home / {userName}:

dotnet new blazorwasm -ho -o BlazingPizza


Após a criação, criaremos o projeto e, finalmente, o executaremos para aproveitar o tipo de resultado (o primeiro comando é executado no nível do arquivo BlazingPizza.sln):

dotnet build


Tudo deve ter passado sem problemas, vá para a pasta Server e execute:

dotnet run


E, finalmente, vemos o resultado estimado:



Ótimo, tudo funciona. Agora não vamos esquecer a aparência do site por um longo tempo e mudar para o seu conteúdo.

O resultado do primeiro comando (dotnet new) foi uma estrutura de pastas:



na minha opinião, esse não é um bom esquema, é melhor que as pastas tenham um prefixo com o nome da solução, é mais familiar (é o que o VisaulStudio faz ao criar novos projetos) e deixa claro que isso não é algum tipo de ext. pasta de serviço. Com o tempo, o projeto pode crescer em muitas pastas que não contêm código C # e, se você for para o Explorer , será difícil navegar para onde, além disso, pode haver alguma implantaçãoscripts baseados no fato de que em cada pasta cujo nome começa com o nome da solução, o projeto está localizado, isso é normal, porque esse é o comportamento de longo prazo do Visual Studio e você pode confiar nele.

Então, renomeamos os projetos e pastas existentes que os contêm (até agora não sabemos nada sobre o IDE , estamos no Linux e, pela primeira vez, não ajudará, porque pastas e projetos são nomeados de maneira diferente):

Client => BlazingPizza.Client
Servidor => BlazingPizza.Server
Shared => BlazingPizza.Shared As

alterações correspondentes devem ser exibidas no arquivo de solução BlazingPizza.sln . Onde o caminho para o projeto “[Servidor] \ ~” for indicado e abaixo, substituiremos de acordo com“BlazingPizza. [Server] \ ~” como na captura de tela abaixo:



Vamos fazer alterações semelhantes no arquivo BlazingPizza.Server.csproj:



E, finalmente, BlazingPizza.Client.csproj deve se parecer com a captura de tela abaixo:



Com as alterações, por enquanto, temos o suficiente acumulou o suficiente para que seria uma pena perder, portanto, conectaremos um sistema de controle de versão à nossa solução. Para fazer isso, finalmente abra nosso IDE Rider e soluções, aqui tudo é igual ao Visual Studio, você pode abrir selecionando um arquivo de solução.

Depois que tudo estiver carregado, vá para o menu VCS => Habilitar integração de controle de versão . E aqui conectamos o Git. O Rider adicionará arquivos de serviço à pasta com a solução e destacará todos os arquivos em vermelho, o que significa que esses arquivos não são corrigidos no sistema de controle de versão.
A primeira vez que o Rider oferecerá a confirmação de tudo o que está no projeto, incluindo o conteúdo das pastas bin e obj , certamente não precisamos disso, tudo porque o arquivo .gitignore que foi adicionado por padrão está vazio. Mova -o para a raiz um nível com o arquivo BlazingPizza.sln e substitua seu conteúdo pelo conteúdo embaixo do kata.

O conteúdo do arquivo .gitignore
# Default ignored files
/workspace.xml
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

# Visual Studio 2017 auto generated files
Generated\ Files/

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

# NUNIT
*.VisualState.xml
TestResult.xml

# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c

# Benchmark Results
BenchmarkDotNet.Artifacts/

# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json

# StyleCop
StyleCopReport.xml

# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc

# Chutzpah Test files
_Chutzpah*

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb

# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap

# Visual Studio Trace Files
*.e2e

# TFS 2012 Local Workspace
$tf/

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user

# JustCode is a .NET coding add-in
.JustCode

# TeamCity is a build add-in
_TeamCity*

# DotCover is a Code Coverage Tool
*.dotCover

# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json

# Visual Studio code coverage results
*.coverage
*.coveragexml

# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*

# MightyMoose
*.mm.*
AutoTest.Net/

# Web workbench (sass)
.sass-cache/

# Installshield output folder
[Ee]xpress/

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish/

# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj

# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/

# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets

# Microsoft Azure Build Output
csx/
*.build.csdef

# Microsoft Azure Emulator
ecf/
rcf/

# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx

# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs

# Including strong name files can present a security risk 
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/

# RIA/Silverlight projects
Generated_Code/

# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak

# SQL Server files
*.mdf
*.ldf
*.ndf

# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser

# Microsoft Fakes
FakesAssemblies/

# GhostDoc plugin setting file
*.GhostDoc.xml

# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/

# Visual Studio 6 build log
*.plg

# Visual Studio 6 workspace options file
*.opt

# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw

# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions

# Paket dependency manager
.paket/paket.exe
paket-files/

# FAKE - F# Make
.fake/

# JetBrains Rider
.idea/
*.sln.iml

# CodeRush
.cr/

# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc

# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config

# Tabs Studio
*.tss

# Telerik's JustMock configuration file
*.jmconfig

# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs

# OpenCover UI analysis results
OpenCover/

# Azure Stream Analytics local run output 
ASALocalRun/

# MSBuild Binary and Structured Log
*.binlog

# NVidia Nsight GPU debugger configuration file
*.nvuser

# MFractors (Xamarin productivity tool) working folder 
.mfractor/

# sqlite
*.db

# Don't ignore server launchSettings.json. We need a specific port number for auth to work.
!**/BlazingPizza.Server/Properties/launchSettings.json


Depois disso, o número de arquivos não confirmados terá que diminuir drasticamente para 31.

Crie nosso primeiro commit na guia Repository:



Digite o commit inicial na descrição do commit e inclua todos os arquivos no commit clicando em “Files Unversioned” => “Add to VCS” :



C a infraestrutura está mais ou menos pronta, vamos falar um pouco sobre o conteúdo da solução.

Conteúdo da solução


BlazingPizza.ClientUI . Startup.cs < pp > . App.razor < pp > index.html .
wwwroot/index.html — , html c < app > .
_framework/blazor.webassembly.js .Net , Mono . .
BlazingPizza.Server
blazorwasm -ho, “Hosted deployment with ASP.NET Core – ASP.NET Core . dotnet -ho , web api. , , , - . Standalone hosting IIS, .
BlazingPizza.Shared( BlazingPizza.DomainModels)
. . , .

: Pizza, , . , - - property. , JavaScript Kotlin.

Renomeie o projeto BlazingPizza.Shared agora para BlazingPizza.DomainModels . Aqui deve-se dizer que Rider, apesar da marca experimental, fez um excelente trabalho. Execute o projeto e verifique se tudo está funcionando e se nada está quebrado.É



bom criar um commit com alterações, apenas para ver o quanto o Rider fez por nós, deixe-me lembrá-lo se fosse o código do Visual Studio , você teria que fazer tudo isso com canetas você mesmo, veja os arquivos diff clicando neles.



Adicionar novos projetos


Adicione mais alguns projetos, comece com BlazingPizza.ViewModels . Ele exibirá os modelos de exibição no cliente. Para adicionar, clique com o botão direito do mouse na solução e selecione Adicionar => Novo Projeto (o tipo de projeto neste e no próximo nesta parte da Biblioteca de Classes )


BlazingPizza.DataAccess.ModelsModelos usados ​​na camada para acessar dados do banco de dados
BlazingPizza.DataAccess.InfrastructureTudo o que é responsável por interagir com o banco de dados
BlazingPizza.Services.InterfacesAs interfaces para serviços são separadas das implementações, de modo que, se você usar outras implementações diferentes do padrão, não será necessário arrastá-las junto com você.
BlazingPizza.ServicesImplementação de serviços, por exemplo, PizzaService , que adicionará pizza ao banco de dados enquanto realiza algum tipo de verificação relacionada à lógica de negócios.

Se algo deu errado, ou simplesmente não quer perder tempo configurando e iniciando imediatamente a partir da segunda lição, a fonte está aqui .

Ah, sim, eu quase esqueci :) Link para o projeto original (Licença MIT) .

Source: https://habr.com/ru/post/undefined/


All Articles