😴
Fighting Javascript Fatigue
      
      
        “Extreme tiredness caused by library churn in Github’s most active community”
      
      
        Fatigue strikes down many… 💤
      
      
      
      
      
        How can we protect our products? 💂
      
      
      
        .NET MVC + Razor templates
      
      
        Migrating to micro service backend 🐜
      
      
        Frontend was busy building Cathedrals ⛪️
      
      
        Monolith Knockout or Angular SPAs
      
      
        Products were tightly
coupled to frameworks 
      
      
        Separate applications for 🖥 and 📱
      
      
        Every rebuild was essentially 👶🛁💦
      
      
        Hackathon #1 🚀
        Node server,
        Universal React,
        Responsive web,
        SEO
      
      
      
        Some time the following week...
      
      
        “How can we protect ourselves
against this churn?”
      
      
        What hurts the most when rebuilding? 🤕
      
      
      
      
      
        Success would mean
decoupling all of these 
      
      
        All of these needed to
outlive any framework…
      
      
        What if we could npm install
our business rules?
      
      
        Focused 👁
Vanilla 🍦
Tested ✅
Pure functions 😇
      
      
      
        import  { refine } from 'seek-refine-job-search';
const params = {
  keywords: 'javascript',
  salaryFrom: 60000
};
const refinements = refine(params);
      
      
        {
  …
  salary: {
    to: [
      { label: '$30k', value: '30000', isActive: false },
      { label: '$40k', value: '40000', isActive: false },
      { label: '$50k', value: '50000', isActive: false },
      { label: '$60k', value: '60000', isActive: true },
      { label: '$70k', value: '70000', isActive: false, refineParams: { … },
      { label: '$80k', value: '80000', isActive: false, refineParams: { … },
      { label: '$100k', value: '100000', isActive: false, refineParams: { … },
      { label: '$120k', value: '120000', isActive: false, refineParams: { … },
      { label: '$150k', value: '150000', isActive: false, refineParams: { … },
      { label: '$200k', value: '200000', isActive: false, refineParams: { … },
      { label: '$200k+', value: '999999', isActive: false, refineParams: { … } }
    ]
  }
  …
}
      
      
        {
  label: '$100k',
  value: '100000',
  isActive: false,
  refineParams: {
    keywords: 'javascript',
    salaryFrom: 60000,
    salaryTo: 100000
  }
}
      
      
        Pure functions are ideal
for unit testing
      
      
        const cases = [
  {
    input: { … },
    output: { … },
    should: 'given input should yield output'
  },
  …
];
      
      
        import  { refine } from 'seek-refine-job-search';
describe('Salary refinement', () => {
  cases.forEach(({ input, output, should }) => {
    it(should, () => {
      expect(refine(input)).to.deep.equal(output);
    });
  });
});
      
      
        How could we prove our
approach to the business?
      
      
        Demo to stakeholders without CSS 🚫💄
      
      
      
        Clarified what is a business rule
and what is the experience.
      
      
        Enormous flexibility with the UX
        Like building a WinAmp skin ⚡️
      
      
        Frameworks 😻 pure functions
      
      
        import { refine } from 'seek-refine-job-search';
// Pass query, receive refinement view model
const refinements = refine(getCurrentQuery());
render() {
  return <RefinePanel refinements={refinements} />
}
      
      
      
        Have we decoupled our
business logic from
the churn?
      
      
        Hackathon #2 🚀
        React Native
        3 developers, 2 days, no iOS experience
      
      
        import { refine } from 'seek-refine-job-search';
const refinements = refine(this.props.params);
render() {
  return (
    <View style={styles.salaryRange}>
      <Text>Min</Text>
      <PickerIOS>
      {
        refinements.salary.from
          .filter(salary => salary.isActive)
          .map(({ value, label }) => (
            <PickerIOS.Item value={value} label={label} />
          ))
      }
      </PickerIOS>
    </View>
  );
}
      
      
      
      
      
      
        Given their importance,
they lacked love 💔
      
      
        Inconsistently replicated,
highly coupled code
      
      
        [
  '/jobs/:worktype',
  '/jobs/in-:location',
  '/jobs/in-:location/in-:suburb',
  '/jobs/in-:location/in-:suburb/:worktype',
  '/jobs/in-:location/:worktype',
  '/jobs/in-:location/:area',
  '/jobs/in-:location/:area/:worktype',
  '/jobs-in-:classification',
  '/jobs-in-:classification/:worktype',
  '/jobs-in-:classification/in-:location',
  '/jobs-in-:classification/in-:location/in-:suburb',
  '/jobs-in-:classification/in-:location/in-:suburb/:worktype',
  '/jobs-in-:classification/in-:location/:worktype',
  '/jobs-in-:classification/in-:location/:area',
  '/jobs-in-:classification/in-:location/:area/:worktype',
  '/jobs-in-:classification/:subclassification',
  '/jobs-in-:classification/:subclassification/in-:location',
  '/jobs-in-:classification/:subclassification/in-:location/in-:suburb',
  '/jobs-in-:classification/:subclassification/in-:location/in-:suburb/:worktype',
  '/jobs-in-:classification/:subclassification/in-:location/:worktype',
  '/jobs-in-:classification/:subclassification/in-:location/:area',
  '/jobs-in-:classification/:subclassification/in-:location/:area/:worktype',
  '/jobs-in-:classification/:subclassification/:worktype'
].forEach(function(friendlyUrl) {
    $stateProvider.state(friendlyUrl.replace(/[\/\:\{\|\}]/g, '_'), {
      url: friendlyUrl,
      resolve: {
        redirect: function () {
          var goToResults = function (searchQueryString) {
            $location.replace().path('/jobsearch').search(searchQueryString);
          },
          goToHome = function () {
            $location.replace().path('/');
          };
          
          return searchStateParamsConverter
            .getQueryStringFromFriendlyUrlStateParams($stateParams)
            .then(goToResults, goToHome);
        }]
      }
    });
});
      
      
        <if url="^/information-communication-technology-jobs(.*)$">
  <redirect url="^/information-communication-technology-jobs/all(.*)?$" to="/jobs-in-information-communication-technology$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/in-(.*)/(.*)?$" to="/jobs-in-information-communication-technology/in-$1/$2$3" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/(full-time|part-time|contract|casual)(.*)?$" to="/jobs-in-information-communication-technology/$1$2" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/architects(.*)?$" to="/jobs-in-information-communication-technology/architects$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/business-systems-analysts(.*)?$" to="/jobs-in-information-communication-technology/business-systems-analysts$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/computer-operators(.*)?$" to="/jobs-in-information-communication-technology/computer-operators$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/consultants(.*)?$" to="/jobs-in-information-communication-technology/consultants$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/database-development-administration(.*)?$" to="/jobs-in-information-communication-technology/database-development-administration$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/developers-programmers(.*)?$" to="/jobs-in-information-communication-technology/developers-programmers$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/engineering-hardware(.*)?$" to="/jobs-in-information-communication-technology/engineering-hardware$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/engineering-network(.*)?$" to="/jobs-in-information-communication-technology/engineering-network$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/engineering-software(.*)?$" to="/jobs-in-information-communication-technology/engineering-software$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/help-desk-it-support(.*)?$" to="/jobs-in-information-communication-technology/help-desk-it-support$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/management(.*)?$" to="/jobs-in-information-communication-technology/management$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/networks-systems-administration(.*)?$" to="/jobs-in-information-communication-technology/networks-systems-administration$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/product-management-development(.*)?$" to="/jobs-in-information-communication-technology/product-management-development$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/programme-project-management(.*)?$" to="/jobs-in-information-communication-technology/programme-project-management$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/sales-pre-post(.*)?$" to="/jobs-in-information-communication-technology/sales-pre-post$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/security(.*)?$" to="/jobs-in-information-communication-technology/security$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/team-leaders(.*)?$" to="/jobs-in-information-communication-technology/team-leaders$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/technical-writing(.*)?$" to="/jobs-in-information-communication-technology/technical-writing$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/telecommunications(.*)?$" to="/jobs-in-information-communication-technology/telecommunications$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/testing-quality-assurance(.*)?$" to="/jobs-in-information-communication-technology/testing-quality-assurance$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/web-development-production(.*)?$" to="/jobs-in-information-communication-technology/web-development-production$1" permanent="true" />
  <redirect url="^/information-communication-technology-jobs/other(.*)?$" to="/jobs-in-information-communication-technology/other$1" permanent="true" />
</if>
      
      
        Over 700 lines of static,
untested XML config… 😩
      
      
      
      
        import { routes } from 'seek-jobs-seo';
export default (
  <Route component={App}>
    <Route path="/" component={Home} />
    {
      routes.map(path =>
        <Route path={path} component={SearchResults} />)
    }
  </Route>
);
      
      
        Router has no knowledge
of friendly urls
      
      
        Handling links inside the app
      
      
        Search Params 👉 Friendly Url
        eg: { classification: 1200 } 👉 '/jobs-in-accounting'
      
      
        Friendly Url 👉 Search Params
        eg: '/jobs-in-accounting' 👉 { classification: 1200 }
      
      
        {
  should: 'qualify valid graduatesearch with work type',
  input: {
    path: '/jobs',
    query: {
      graduatesearch: 'true',
      worktype: '242'
    }
  },
  output: { path: '/jobs/graduate/full-time', query: {} }
}
      
      
        import { qualifyUrl } from 'seek-jobs-seo';
render() {
  const { path, query } = qualifyUrl(locale, path, query);
  
  return (
    <Link to={`${path}?${query}`}>
      { children }
    </Link>
  );
}
      
      
      
        Friendly URLs,
Qualifying,
Meta Descriptions,
Canonicals
      
      
      
        All vanilla 🍦
Framework agnostic 🙈
Protected from the churn 👮
      
      
        How can we reduce friction
to separating modules? 😬
      
      
        Module boundaries are
not always obvious 🕵
      
      
        How can we stage modules
while proving out the api
      
      
        Webpack
        Resolving module directories
      
      
        // Webpack config
module.exports = {
  …
  resolve: {
    modulesDirectories: [ 'node_modules', 'wip_modules' ]
  }
  …
};
      
      
        /app
  /components
  /node_modules
  /wip_modules
    /seek-jobs-api-client
    
      
      
        Using staging modules:
        import { search } from 'seek-jobs-api-client';
      
      
        Extract 🛫
Test ✅
Document 📜
Install 🛬
      
      
        Using `node_modules`:
        import { search } from 'seek-jobs-api-client';
      
      
      
        If unit testing happens outside,
what happens inside? 🤔
      
      
        Find confidence without
testing yourself into a corner.
      
      
      
        The application is an integration module
      
      
        Want to avoid re-coupling with tests 🔗
      
      
        We’ve seen this pay off already 🤑
      
      
        Needed to introduce a
state management library
      
      
        Enter Flummox…
        npm install flummox --save
      
      
        Universal 🌏
Light weight 🍃
Clean API 👌
      
      
      
      
        Churn had struck.
Fatigue was setting in. 😴
      
      
        Switched to Redux
        npm install react-redux --save
      
      
      
        Tests provide confidence
to refactor, not hinder it 💪
      
      
      
        Suffered just like our business logic 😔
      
      
        Rebuilding the same guidelines
again and again and again
      
      
        Each time coupled to a framework 🔗
      
      
      
        Standardised on preprocessor — LESS
      
      
        Can we share design guidelines
if not components? 🛤
      
      
        // Webpack config
module.exports = {
  …
  resolve: {
    modulesDirectories: [ 'node_modules', 'wip_modules' ]
  }
  …
};
      
      
        Leverage staging modules
        /wip_modules
  /theme
    /grid
    /palette
    /type
    /theme.less
    
      
      
        theme.less
        @import (reference) 'grid/grid.less'
@import (reference) 'palette/palette.less'
@import (reference) 'type/type.less'
      
      
        palette.less
        @sk-blue: #0d3880;
@sk-pink: #e60278;
@sk-green: #178a00;
      
      
        type.less
        @base-font-stack: "Helvetica Neue", HelveticaNeue, Helvetica, Arial, sans-serif;
@base-font-size: 10;
@font-descender-height-scale: 0.12;
@hero-type-row-span: 5;
@hero-type-scale: 4.2;
@headline-type-row-span: 4;
@headline-type-scale: 2.8;
@heading-type-row-span: 3;
@heading-type-scale: 2.1;
@subheading-type-row-span: 3;
@subheading-type-scale: 1.8;
@standard-type-row-span: 2;
@standard-type-scale: 1.4;
      
      
        No resulting CSS, just guidelines 📐
      
      
        @import (reference) '~theme'
.button {
  .touchableText();
  background-color: @sk-pink;
  color: @sk-white;
  cursor: pointer;
}
      
      
        import styles from './button.less';
render() {
  return (
    <button className={styles.button}>
      { children }
    </button>
  );
}
      
      
        CSSModules gives us style encapsulation 💊
      
      
        CSSModules protect us
from framework churn ⚔
      
      
        Same styles could be imported
to an Angular component 💣
      
      
        CSSModules gives our design guidelines
what vanilla gives our business logic 💂
      
      
        What if we could share the components… 🤔
      
      
        Hackathon #3 🚀
        Living Style Guide 🎨
      
      
      
        Core design principles 📐🖍
      
      
        Suite of React components
that implement the guidelines
      
      
        import { Button, StarIcon } from 'seek-style-guide/react';
render() {
  <Button>
    <StarIcon />
    Save
  </Button>
}
      
      
      
        Huge development speed up 🏎
      
      
        Makes consistent visual style easier
      
      
        Style guide is now powering 5 products 🔌
      
      
        Canonical list of core
components and guidelines
      
      
        Switching from React is
essentially swapping templates
      
      
      
      
        Micro library mentality
focusing of pure functions 😇
      
      
        “Best way to reduce complexity
in your app is to take it away”
      
      
        Choose frameworks that
solve actual problems
        Beware of blogs
      
      
        Engineer for change, it’s inevitable
      
      
      
        
        @michaeltaranto
        github.com/mjt01
        bit.ly/javascript-fatigue