os desafios do desenvolvimentode front-end em um e-commerce
@shiota 2013
olá!slideshare.net/eshiotagithub.com/eshiota
@shiota
front-end engineer@
DEV
e-commerce 101em alguns slides
=)
taxa de conversãodos usuários que entram no site, quantos
finalizam uma compra?
ticket médioem média, quanto os usuários gastam
por compra?
=)taxa de conversão
!ticket médio
=
taxa de conversão!
ticket médio= $
= $=)
= ?=)
complexoindecisoexigente
inexperientedecidido
cuidadosoexperiente
ser humano, como todos nós =)
o que faz o usuário abandonar o carrinho?
alto custo de frete
$
não estão prontos para finalizar
?
produtos muito caros$
guardam para depois
não mencionou claramente o frete
?
sem guest checkout
formulário com muitas informações
checkout complexo
website lento
taxas extras
falta de opções de pagamento
entrega demorada
spam de ofertas
site não funciona=(
como o front-end pode melhorar a conversão?
formulário com muitas informaçõescheckout complexo
website lentosite não funciona
velocidade da páginainterface estável
detalhes = emotion designvalidação de novas hipóteses
desafios de front-end(agora a palestra começa =P)
múltiplos desenvolvedoresdesenvolvimento escalável
performance client-sidetestes a/b
trabalhando comvários desenvolvedores
trabalhar em equipe é difícil... =(
aspas simples!
aspas duplas
ponto e vírgula no JS!
sem ponto e vírgula
JavaScript!
Co"eeScript
(JavaScript, claro)
(com ponto e vírgula)
... mas cada um pode ter uma visão diferente e complementar. =)
code smell performance
sintaxe arquitetura
mantenha um code standard para o time.
consistência, legibilidade, sem bikeshed.
git + pull requests
qualquer um revisa, qualquer um comenta.
diferentes visões,mais erros detectados.
o conhecimento é disseminado pelo time.
todos ficam maiscriteriosos com o que fazem.
desenvolvimentoescalável e testável
desenvolvimento ágil:mudanças precisas, altos ganhos.
melhorias são constantes,e nada é 100% definitivo.
o código deve ser facilmente alterável/adaptável.
dica 1usem pre-processors de CSS. sério. agora. já. eu espero.
sass+
variáveis, mixins e funções
/*********************************************************************** Variables Module** All constants that will be used through the styles must be* defined here.**********************************************************************/
$TEXT_COLOR : #555;$DISCOUNT_COLOR : #ef6565;$LIGHT_COLOR : #fefafa;
$SELECTION_BACKGROUND : #41bdce;$SELECTION_COLOR : #fff;
$LINK_COLOR : #447f87;$LINK_HOVER_COLOR : #41bdce;$LINK_ACTIVE_COLOR : #447f87;
$ERROR_BACKGROUND : #fffaad;$LIGHT_BACKGROUND : #fefefa;
$SITE_WIDTH : 978px;$FOOTER_HEIGHT : 777px;
$PURPLE : #905194;$ORANGE : #fbb100;
/*********************************************************************** Mixins Module** All general purpose mixins are defined here.**********************************************************************/
/********************************************************************** =Clearfix*********************************************************************/
@mixin clearfix { &:after { clear: both; content: " "; display: block; font-size: 0; height: 0; visibility: hidden; }
zoom: 1;}
/********************************************************************** =Image replacement** `display` property should be declare on the element, not here* on the mixin. Element must have fixed width and height.*********************************************************************/
@mixin img_replacement { text-indent: 100%; overflow: hidden; white-space: nowrap;}
/*********************************************************************** Functions Module** Custom functions used by the application**********************************************************************/
// Returns unitless number@function remove-unit($number) { $unit: unit($number); $one: 1;
@if $unit == "px" { $one: 1px; } @if $unit == "em" { $one: 1em; } @if $unit == "%" { $one: 1%; }
@return $number / $one;}
// Returns flexible value// Returns `em` by default, accepts `%` as format.@function flex($target, $context: 14, $unit: "em") { $size: remove-unit($target) / remove-unit($context);
@if $unit == "em" { @return #{$size}em; } @if $unit == "%" { @return percentage($size); }}
// Alias to `flex` function, using `%` as format.@function perc($target, $context) { @return flex($target, $context, "%");}
// Alias to `flex` function, using `em` as format.@function em($target, $context: 14) { @return flex($target, $context, "em");}
estilos modularizados em partials
app/ assets/ stylesheets/ base/ _functions.scss _mixins.scss _variables.scss ui/ _breadcrumb.scss _carousel.scss _dentedBox.scss _flashMessage.scss
/********************************************************************************* UI > Breadcrumb** General styles for the breadcrumb.********************************************************************************/
.breadcrumb { font-size: em(12px); line-height: em(21px, 12px); text-transform: uppercase; color: #444; width: 978px;}
.breadcrumb a,
.breadcrumb a:visited,
.breadcrumb a:active { color: #444; text-decoration: none;}
.breadcrumb a:hover { color: #444; text-decoration: underline;}
.breadcrumb .separator { padding: 0 3px;}
/********************************************************************************* UI > Loader** Animated loader for AJAX requests********************************************************************************/
@mixin loader_sprite_position($xoffset, $yoffset) { background-position: sprite-position($icon-sprite, loader_sprite, $xoffset, $yoffset);}
.loader { width: 25px; height: 25px; display: none;}
.loader b { display: block; width: 25px; height: 25px; background-image: sprite-url($icon-sprite);}
.loader b,
.loader .f1 { @include loader_sprite_position(-10px, -10px); }
.loader .f2 { @include loader_sprite_position(-45px, -10px); }
.loader .f3 { @include loader_sprite_position(-80px, -10px); }
.loader .f4 { @include loader_sprite_position(-115px, -10px); }
.loader .f5 { @include loader_sprite_position(-150px, -10px); }
.loader .f6 { @include loader_sprite_position(-185px, -10px); }
.loader .f7 { @include loader_sprite_position(-220px, -10px); }
.loader .f8 { @include loader_sprite_position(-255px, -10px); }
geração automática de sprites acelera o desenvolvimento.
$icon-sprite: sprite-map("icon/*.png", $spacing: 16px, $repeat: no-repeat, $layout: vertical);
$icon-sprite: sprite-map("icon/*.png", $spacing: 16px, $repeat: no-repeat, $layout: vertical);
/* Compass sprite function receives the map variable and image as arguments */
background: sprite($icon-sprite, arrow_dropdown) no-repeat;
/* Compiled CSS */
background: url(/assets/icon-s5dab8c2901.png) -40px -158px no-repeat;
função de inline image economiza requests.
/* Compiled CSS */
background: #f5f3fb url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQCAMAAAAcVM5PAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAlQTFRF5+TW////////4qZUpQAAAAN0Uk5T//8A18oNQQAAACBJREFUeNpiYGBgAgMGBkYog4mJXAbILAiDkVxzAAIMAEMOAPMId2OWAAAAAElFTkSuQmCC') repeat;
/* Generates a base64 image */
background: #f5f3eb inline-image("bg_dots.png") repeat;
(seja criterioso)
dica 2módulos: poucas linhas, comportamentos isolados, extensíveis, e testáveis.
estrutura base (reset, base styles)
grid
padrões
módulos
módulos contextualizados
css do módulo
/********************************************************************************* UI > Loader** Animated loader for AJAX requests********************************************************************************/
@mixin loader_sprite_position($xoffset, $yoffset) { background-position: sprite-position($icon-sprite, loader_sprite, $xoffset, $yoffset);}
.loader { width: 25px; height: 25px; display: none;}
.loader b { display: block; width: 25px; height: 25px; background-image: sprite-url($icon-sprite);}
.loader b,
.loader .f1 { @include loader_sprite_position(-10px, -10px); }
.loader .f2 { @include loader_sprite_position(-45px, -10px); }
.loader .f3 { @include loader_sprite_position(-80px, -10px); }
.loader .f4 { @include loader_sprite_position(-115px, -10px); }
.loader .f5 { @include loader_sprite_position(-150px, -10px); }
.loader .f6 { @include loader_sprite_position(-185px, -10px); }
.loader .f7 { @include loader_sprite_position(-220px, -10px); }
.loader .f8 { @include loader_sprite_position(-255px, -10px); }
css do módulo contextualizado
// On ui/_buttons.scss
.bt-wrapper .loader { position: absolute; z-index: 4; right: 20px; top: 50%; margin-top: -9px;}
// On modules/_checkoutAddressForm.scss
.address-form .cep-input .loader { position: absolute; right: -33px; top: em(29px);}
javascript enxuto, auto-contido.
// Implements the animated loader for AJAX requests
// Loader constructor//// * `placement`: Function that determines the loader's placementns("EDEN.ui.Loader", function (placement) { if (!(this instanceof EDEN.ui.Loader)) { return new EDEN.ui.Loader(placement); }
this.frame = 1; this.framesQty = 8; this.stack = []; this.animating = false; this.$loader = $("<div class='loader'><b> </b></div>"); this.$renderer = this.$loader.find("b"); this.placement = placement;
this.init();});
EDEN.ui.Loader.prototype = {
// Properties // ----------
// Animation speed (in frames per second) fps : 20,
// Fading speed fadeSpeed : 150,
// Public methods // --------------
testável!
describe("EDEN.ui.Loader", function () { var Loader = EDEN.ui.Loader;
beforeEach(function () { loadFixtures("loader.html"); });
afterEach(function () { $("body").find(".loader").remove(); });
it("accepts instance creation without new operator", function () { var newLoader = Loader();
expect(newLoader).toBeInstanceOf(Loader); });
it("inits the loader on creation", function () { var loader , oldInit = EDEN.ui.Loader.prototype.init ;
EDEN.ui.Loader.prototype.init = jasmine.createSpy();
loader = new Loader();
expect(loader.init).toHaveBeenCalled();
EDEN.ui.Loader.prototype.init = oldInit; });
it("appends the loader to body as a default", function () { var loader = new Loader();
expect($("body").find(".loader").length).toEqual(1); });
it("appends the loader through an argument function", function () { var loader = new Loader(function ($loader) { $("#loader-placeholder").append($loader); });
expect($("#loader-placeholder").find(".loader").length).toEqual(1); });});
"Mas tem muita coisa que não dá pra testar, né?"
"Mas testes atrapalham a entrega do projeto, né?"
a Baby possui 1144 specs de JavaScript até agora
falhas no jshint ou nas specs de javascript quebram o build
dica 3javascript desacoplado e modularizado
mediator: ponto central de comunicação via pub/sub
MEDIATOR
nenhum módulo tem conhecimento do outro
MEDIATOR
Mediator, me avisa quando sair o novo do Game of
Thrones?
Blz
MEDIATOR
Mediator, me avisa quando sair o novo do Mythbusters?
É nóish.
MEDIATOR
Mediator, saiu um eppy novo de Game of Thrones.
Subscribers, saiu um eppy novo de Game of Thrones!
Ae, vou baixar, acho que vai ser feliz e tal
=D
MEDIATOR
Mediator, saiu um eppy novo de Mythbusters.
Subscribers, saiu um eppy novo de Mythbusters!
Ae, vou baixar!
os módulos só conhecem o mediator
módulos desacoplados, com comportamentos específicos e isolados
// Code inside ShippingAddressForm
_registerInterests : function () { this.element.find(".cep-input") .on("keyup paste cut", this._onCepModification.bind(this)); },
_onCepModification : function (event) { if (this.isCepFilled()) { EDEN.mediator.trigger("shipping-cep-change", event.target.value); } else { EDEN.mediator.trigger("shipping-cep-incomplete", event.target.value); }}
// Code inside checkoutModule
_registerInterests : function () { EDEN.mediator.on("shipping-cep-change", this._onShippingCepChange, this); this.shippingService.on("get-success", this._onShippingGetSuccess, this);},
_onShippingCepChange : function (cep) { this.shippingService.get(cep);}
_onShippingGetSuccess : function (data) { EDEN.mediator.trigger("shipping-rate-change", data.rate); EDEN.mediator.trigger("delivery-estimate-change", data.estimate);}
// Code inside purchseInfo
_registerInterests : function () { EDEN.mediator.on("shipping-rate-change", this._onShippingRateChange, this); EDEN.mediator.on("delivery-estimate-change", this._onDeliveryEstimateChange, this);},
_onShippingRateChange : function (rate) { this.updateShippingRate(rate);},
_onDeliveryEstimateChange : function (days) { this.updateDeliveryEstimate(days);},
updateShippingRate : function (rate) { var formatter = EDEN.currency.formatter;
this.element.find(".shipping-rate").text(formatter(rate)); this.shippingRate = rate;
this.updateTotal();},
updateTotal : function () { var total = this.subtotal + this.shippingRate, formatter = EDEN.currency.formatter;
this.element.find(".total").text(formatter(total));
EDEN.mediator.trigger("cart-total-change", total);}
// Code inside installmentSelector
_registerInterests : function () { EDEN.mediator.on("cart-total-change", this._onCartTotalChange, this);},
_onCartTotalChange : function (total) { this.updateInstallments(total);},
updateInstallments : function (total) { // Updates the values}
você não precisa saber tudo isso de primeira.
addyosmani.com/largescalejavascript
aprenda javascript antes de se focar em um framework.
performanceclient-side
css/javascriptminification/compression
lazy-load everything! o/
sprites e imagens inlines
sass+
não abuse de font-faces
testes a/b
isole os estilos e JS em classes, partials e módulos totalmente separados
<nav id="site-menu" class="site-menu"> <div class="site-menu-container"> <% if new_header? %> <%= render "layouts/open_site_nav" %> <% else %> <%= render "layouts/site_nav" %> <% end %>
<% unless new_header? %> <%= render "layouts/search" %> <% end %> </div></nav>
/******************************************************************************** =Menu A*******************************************************************************/
.site-header-old .user-menu { position: absolute; right: perc(261px, $SITE_WIDTH); cursor: pointer; width: 213px; height: 63px; overflow: hidden; z-index: 600;}
/******************************************************************************** =Menu B*******************************************************************************/
.site-header-new .user-menu { position: absolute; right: perc(261px, $SITE_WIDTH); width: perc(150px, $SITE_WIDTH); height: em(63px); overflow: hidden; z-index: 600;}
AB-TESTING.md - como remover a versão perdedora
# A/B Testing on Baby Site
This document lists all A/B tests currently being run on the project, andshortly introduces the method being used.
## Tests currently being run
### Site-wide
#### Header design version
* Test name: `header-version`* Starts at: `ApplicationController`, on `:before_filter`* Goal: When user goes to a success checkout page* Ends at: `orders#success` view* PR/Commits: [#664](https://github.com/Baby-com-br/troy/pull/664)
To remove this test:* Remove the `new_header?` method and its `:helper_method` on `application_controller.rb`* Remove the `header_version` method and its `:helper_method` on `application_controller.rb` and ALL its calls.* Consolidate the correct `render` calls on `layouts/_header.html.erb` and `layouts/_site_menu.html.erb`* Remove the `site-header-<%= header_version %>` class on `layouts/_header.html.erb`* Remove the `header-version-<%= header_version %>` class on `layouts/_head.html.erb`, on the `<body>` tag* Remove the `finished` call on `baby-site/app/views/orders/success.html.erb`* On `modules/_mainSearchForm.scss`, remove the entire block related to the loser version, and on the winner version: (1) remove the comment header about the A/B test, (2) unprefix all selectors by removing either `.site-menu` (if the old header won) or `.site-header` (if the new header won)* On `layout/_user_menu.scss`, remove the entire block related to the loser version, and on the winner version: (1) remove the comment header about the A/B test, (2) unprefix all selectors by removing either `.site-header-new` (if the old header won) or `.site-header-old` (if the new header won)* On `ui/_section_header.scss`, remove the `.header-version-old .section-titles` and `.header-version-new .section-titles` blocks, and use the winner padding on `.section-titles`.* On `sections/_profile.scss`, remove the `.header-version-old .profile-header .site-menu` and `.header-version-new .profile-header .site-menu` blocks, and use the winner padding on `.profile-header .site-menu`.* On `layout/_main.scss`, delete the `.header-version-old .site-menu-container` block. If the old version won add the `@include centered_block` on `.site-menu-container`. If the new version won, just delete the aforementioned block.* If the new header won, remove the `inline/bg_search.png` image from the project.* Remove either the `modules/_categoryMenuModule.scss` or `modules/_openSiteNav.scss`, and its call from `application.scss`* Remove either the `modules/categoryMenuModule.js` or `modules/siteNavModule.js`, and its call on `commands/beforeCommand.js`.* Remove the CE_SNAPSHOT_NAME from `home/index.html.erb`, `products/show.html.erb` and `products/search.html.erb`
### Checkout page
#### Cart update
* Test name: `cart-update`* Starts at: `orders#new` view* Goal: When user goes to a success checkout page.* Ends at: `orders#success` view* PR/Commits: [#616](https://github.com/Baby-com-br/troy/pull/616)
To remove this test:* Remove the `<%= cart_update_enabled = ab_test('cart-update', 'no', 'yes') %>` line on `baby-site/app/views/orders/_cart_products_list.html.erb`* Remove all related code on `_cart_products_list.html.erb` related to the option that lost* Remove CE_SNAPSHOT_NAME script* Remove the `finished` call on `baby-site/app/views/orders/success.html.erb`
## Running an A/B test
Troy uses the [Split gem](https://github.com/andrew/split). Quick instructions:
* Use the `ab_test("my_test", "control_variable", "hyphothesis_variable")` helper* Use the `finished("my_test")` helper to track goals.* If the test is inside a fragment cached block, you'll have to create a new key. Check [this page](https://github.com/andrew/split/wiki/Caching) for further info.* Write an acceptance test to ensure all variants work* Update this file, listing the test location, name, purpose, commits or PR related to it, and any specific instructions on removing it after it's complete.
shiota, um dev front-end precisa saber back-end?
fulano(a), eu preciso saber cozinhar ou lavar roupa?
não, mas ajuda, né? ;D
você não precisa ser um nando vieira*.
* @fnando - faz design, front-end, manja JS pacas, é um dev Ruby f*odido, e manja de SysOps
saber back-end melhora seu código.
saber back-endlhe dá mais controle.
saber back-endmelhora a comunicação.
quando você deixa de perguntar apenas "como vou fazer isso" e passa a perguntar "como vou fazer isso da melhor maneira"...
... você está no caminho certo.
divirta-se. sempre. =)
obrigado!slideshare.net/eshiotagithub.com/eshiota
@shiota