We decided on building the mobile dashboard next for a few reasons. First, the dashboard is one of the most important parts of Tumblr—for many people it is their primary window into the content here. Because of that, most things that we develop inevitably touch the dashboard. It was time to raise the stakes.
The desktop dashboard and the mobile apps have been moving forward at a blistering pace, but have you seen the mobile web dashboard? It was stuck in 2015. Nobody ever made features for it because it was a completely separate codebase. We avoided that issue with the rewrite. Since the new version we’re building is responsive, our new mobile web dashboard will eventually also power the desktop web dashboard experience. This’ll make it much easier to maintain feature parity between the desktop and mobile web experiences.
Finally, we wanted to make something a little bit more complex than the image page, and the mobile web dashboard seemed like a good next step. It allowed us to start making authenticated API calls and figuring out how to pass cookies through our new server to and from our API; to think about laying the groundwork for a totally responsive dashboard, so we can eventually launch this for the desktop; to make a testing ground for progressive web app features like web app manifests; to start rendering posts that are backed by the new post format; and to figure out how to slowly roll out the new stack to a consistent, small set of users. We also had a pretty bad performance bug and learned a whole lot about profiling Node. (We plan on writing more in-depth posts about some of these topics soon.)
It is good
And the rewrite turned out really well. Even with more features like pull-to-refresh, a new activity popover, and modern audio and video players, we sped up our DOM content loaded time by 35%! The new page has a lot of modern web standards in it too like srcsets, flexbox, asynchronous bundle loading, and a web app manifest. You can install the mobile web dashboard as a standalone app now. It was also way faster and simpler to write using React than it was on our old PHP/Backbone stack.
We’re really proud of the new mobile web dashboard. We look forward to bringing even more new features to it in the near future, and launching even more pages on our new web infrastructure.
If you’ve been following this Tumblr, you’ll likely know that we, the Core Web team, have recently started rewriting and modernizing the Tumblr web platform. This undertaking presents some incredibly exciting opportunities to innovate with lots of fun technologies. We’re working on improving every aspect of the web; the dashboard, the archive, the blog network, you name it.
Are you a senior JavaScript engineer and wanna be a part of this adventure? Come join Core Web!You’ll help create the building blocks with which a brand new modern Tumblr will be built. Your work will directly impact and define the user experience for millions of users and the development tools for a large number of product engineers across several teams at Tumblr!
What you’ll do
We’re looking for an extraordinary senior JavaScript engineer who wants to take on the following challenges:
Keep making our build and deployment more delightful and futuristic
Help establish norms and standards for how this new web client should be architected, including setting JavaScript, CSS, performance and other best-practices, and introducing/creating the tools to achieve them
Internally and externally raising awareness around the work the team is doing by being active in the Open-source and engineering community
Whatever else you think will help us create the highest quality web platform and development experience!
Who we’re looking for
An ideal team member is someone with:
Strong JavaScript and CSS fundamentals
Experience setting up Continuous Integration / Continuous Deploys
Expertise in build tools like Webpack, Parcel (or similar)
Pragmatism and the ability to decide what’s “good enough” (while planning ahead and knowing when to iterate)
An ability to independently drive projects
A desire to innovate and bring new things into the world
An understanding of code quality, unit test coverage, and performance
In the ten years that Tumblr’s been around, a lot has changed in web technology. We’ve kept up, of course, but it’s always been a process of addition, layering one new technology on top of another. And what we were working with—a custom framework built on top of Backbone, messily entangled with a PHP backend and its associated templates—was becoming unmanageable. Our piecemeal conversions to new technologies meant we had thousands of ways posts were rendered (only a moderate exaggeration). And each of those had to be updated individually to support new features or design changes.
It was time to step back, survey the world of web technology, and clean house in a big way. That we could finally test some of the new tech we’ve been itching to use was just a little bonus.
We started by laying out our goals:
A web client codebase fully separated from the PHP codebase that gets its data from the API in the same way our mobile apps do
A development environment that’s as painless as possible
Built on a framework with a healthy and active community, with some critical mass of adoption
With those goals in mind, we spent the beginning of the year on research - figuring out what kinds of things people were building web apps with these days, tooling around with them ourselves, and trying to assess if they would be right for Tumblr. We landed, eventually, on React, with a Node server (running Express) to make isomorphism as easy as possible. On top of that, we’re using Cosmos for developing components, React Router for routing, and TypeScript to make our lives better in general. (My colleague Paul already wrote about what went into our decision to use TypeScript here.)
As if writing an entirely new stack wasn’t enough, we realized along the way that this was our perfect chance to start deploying containerized applications with Kubernetes, a first for Tumblr. We had never previously deployed a node application to production here, and didn’t have the infrastructure for it, so it was a perfect green field on which to build another new and exciting thing. There’ll be more to come later on Kubernetes.
So where are we now? Well, we’ve launched one page powered by this new app - image pages, like this - with more to come very soon.
Though it may seem simple, there’s a whole new technological world between you clicking that link and seeing that page. There’s a ton more exciting stuff happening now and still to happen in the future, and we’re looking forward to sharing it here. Wanna get in on the action yourself? Come work with us: https://www.tumblr.com/jobs.
Flux helped bring the complexity of Data Lasso down, replacing messy event bus structure. React helped make the UI more manageable and reduce code duplication. More below on our experience.
One of the Core Web team’s goals at Tumblr is to reduce the number of runtime issues that we see in our React codebase. To help move some of those issues from runtime to compile time, I evaluated the two leading type systems, Flow and TypeScript, to see if they could give us more type safety. I did a bit of background reading about the differences between Flow and TypeScript to see what the community had to say about them.
This post claims that Flow and TypeScript are similar enough that you should choose whichever of them is easier to integrate with your other tools. For Angular development, it recommends using TypeScript; for React, Flow.
This post outlines the author’s experience with using Flow in a React codebase. It advocates switching from Flow to TypeScript because of Flow’s unhelpful error messages, bad tooling, and propensity to spread untyped code. It also claims that most of the type annotations are able to be shared between Flow and TypeScript with only minor changes.
This slideshow shows many differences around the philosophies and goals of TypeScript and Flow, and it gives detailed explanations in the differences between the two type systems. It explains IDE support and how to get access to third-party type definitions.
Lack of Consensus
It seems like many people have differing opinions about which type system is better for a React codebase. Because there wasn’t a broad consensus across the community, I decided to get some first-hand experience with each of these tools to see which one would be most practical and helpful for use at Tumblr.
For the most part, Flow and TypeScript are basically interchangeable. I was able to reuse most of the source code between both projects with only minor changes. Here are some examples of changes I needed to make to get my TypeScript code working with Flow:
Flow requires that types are imported using import type where TypeScript re-uses import.
Some generic type constraints are different in redux’s type declarations between Flow and TypeScript, so I dropped the generic constraint for Flow.
Types cannot have the same name as constants, so I had to rename a few small things (see below).
Testing
After I got the project prepared I set up the following situations to see which tool performed better. These are my assumptions of the most common situations in which a type checker will help when writing React code on a day-to-day basis.
Handling an Unnecessary Case in a Switch
TypeScript
TypeScript realizes that 'not_real' is not a possible case for the switch.
Flow
Flow does not detect any issue.
Declaring Variables with Same Name as Type
TypeScript
TypeScript allows types to have the same name as constants, and it allows Command-clicking on the types to see their declarations.
Flow
Flow requires types and constants to have different names. In this case, I needed to rename the type to INCREMENT_ENTHUSIASM_T to appease Flow’s type checker.
Returning Incorrect Type from Function
TypeScript
[ts]
Type '{ enthusiasmLevel: string; languageName: string; }' is not assignable to type 'StoreState'.
Types of property 'enthusiasmLevel' are incompatible.
Type 'string' is not assignable to type 'number'.
Flow 0.52
[flow] object literal (This type is incompatible with the expected return type of object type Property `enthusiasmLevel` is incompatible:)
Flow 0.53
[flow] property `enthusiasmLevel` of StoreState (Property not found in number) [flow] property `languageName` of StoreState (Property not found in number)
Missing Required Props When Instantiating a Component
TypeScript
TypeScript shows the error at the site where the properties are missing with the error:
[ts] Type '{}' is not assignable to type 'IntrinsicAttributes & Props'. Type '{}' is not assignable to type 'Props'. Property 'name' is missing in type '{}'.
Flow
Flow shows the error within the component where the property will be used, with no way to discover which call site is missing a property. This can be very confusing in codebases that have lots of reusable components. Flow displays this error:
[flow] property `name` of Props (Property not found in props of React element `Hello`)
Code Safety
TypeScript
TypeScript allows enforcing full type coverage on .ts files with the noImplicitAny flag in the tsconfig.
Flow
Flow provides a code coverage plugin so that you can see which lines are implicitly not typed.
Other Considerations
Flow has the most React community support and tooling, so there is much more documentation about how to get Flow and React working together. TypeScript is more popular with Angular developers. Choosing TypeScript may be breaking from community standards, so we may have more issues that don’t have a simple answer on Google.
Conclusion
I concluded that we should use TypeScript because it seems easier to work with. My experience seems to line up with this blog post. It has better error messages to debug type issues and its integration with VSCode makes coding more pleasant and transparent. If this ends up being the wrong choice later on, our codebase will be portable to Flow with some minor changes.
Shortly after arriving at this conclusion, Flow 0.53 was released and a blog post on Medium published touting it’s “even better support for React”. However, after running through the test cases above, I only found one case where Flow had improved its error messaging. TypeScript still seems like the more reliable, easier to use solution.
Naturally, the heaviest strain on the network comes from the content - filled with heavy gifs and other content. However, we also load embarrassingly large amounts of JavaScript. There is one particularly heavy JavaScript file that contains our vendor libraries (e.g., jQuery, Backbone, etc), which we call the vendor bundle.
It was loaded right on the top of the page, significantly adding to the critical rendering path. But we don’t need it there, being one of the first things user loads. Moving the vendor bundle down the page might result in performance gains and a more responsive page load time - all are great goals that Tumblr’s Core Web team is set on accomplishing.
After a few months of patching ancient, legacy inline scripts, and running performance tests, the script is finally moved to the bottom of the page! Even more, with this effort we decided to really dig into performance - we conducted A/B testing of that move (establishing performance split testing framework along the way) to see what exactly what effect the move would have on our site’s performance.
Results
Starry eyed and excited to see improvements across the board (that file exists on nearly every page of tumblr, after all) we jumped head on into the data to find ourselves amused and slightly disappointed.
Turns out, the performance gains that we expected to see from moving a heavy file out of a critical rendering path were not there. The 75th percentile of page load times across the board remained the same. We suspected that analyzing on a more granular would reveal performance gains on certain pages or even certain browsers - but results were pretty uniform. See for yourself - below are boxplots for performance on the dashboard:
Was our approach to taking measurements incorrect? We revisited numbers again and again. We looked into finer detail at various pages. We excluded IE9 after hypothesizing that it might skewing our results. We measured across several metrics that we had at our disposal (we sample actual navigation timings from actual users). Results remained the same.
Outcome
As much as we were disappointed, we were also glad that we ended up knowing precisely what effect we had on the performance. Oftentimes we take blind faith that best practices will lead to best results, and fail to realize that myriads of factors weigh in on actual outcomes.
If anything - we learned a lot from this experience. We gained better insight into our codebase and established an approach to measuring performance in future experiments!
If you’re an avid user of Tumblr on mobile web, then you might’ve noticed some improvements we made. Bigger font sizes and higher contrast text? Your screen reader actually reads what you hope it would? You’ve guessed it, we’re making Tumblr ✨accessible✨.
Why?
Since we’re rewriting the web, we wanted to make sure we did so with accessibility in mind. I could give you a long description why, but plenty of articles explain better than I can. Put simply: the web should be made useable for everyone.
We began with using the accessibility auditing tool in Google Lighthouse to check the improvements that could be made. Initially, our score wasn’t that great: 62. If you factored in areas that need to be manually checked then our score would have been abysmal. However, we’ve made great strides since then and are on our way to achieving that coveted 💯
We had inaccessible menus and poorly described elements, among other things. Using a tool like VoiceOver or TalkBalk you can see what experiencing Tumblr on mobile web with a screen reader was like. Here’s a gif showing what the mobile web experience on Tumblr was like prior to the changes.
What we did
Some of the more noticeable improvements we made were introducing design changes to increase readability and making improvements following WAI-ARIA guidelines. We’ll walk through a few other changes we made using React.
Visual order on the page follows DOM order
One of the larger changes we made was to revamp modals and popovers (e.g., the post activity screen). Originally we used React Portals but it isn’t always the most friendly for accessibility. Ideally you want to have elements appear in logical DOM order and Portals provides a way to circumvent that. So, no more Portals!
The user’s focus is directed to new content added to the page
Next step was to provide a way to manage focus. We want to a) direct focus to the modal when it’s opened and b) return focus to the element that opened the fullscreen modal. Using React’s lifecycle methods and refs, this is simple enough to implement. In your modal component:
public targetEl: HTMLElement; // The element used to open the modal public buttonEl: HTMLElement;
publiccomponentDidMount() { // We add an event listener to get the element that opened the modal document.addEventListener(‘focus’, this.setOriginalTargetEl, true); // We set focus to some element inside your modal this.buttonEl.focus();
}
publiccomponentWillUnmount() { // Return focus to the element that opened the modal if (this.targetEl) { this.targetEl.focus();
}
}
publicsetOriginalTargetEl= event => { // Only set it once to get the initial target if (!this.targetEl) { this.targetEl = event.relatedTarget; document.removeEventListener('focus’, this.setOriginalTargetEl, true);
}
};
Of course, we’re still fine-tuning different elements of the site since accessibility is more than just a number. A lot of these changes will be even more noticeable when the new Tumblr dashboard comes to your desktop. There’s still more to come, so keep your eyes open!
Think there’s a way to make Tumblr more accessible? Hit us up at tumblr.com/jobs and come work with us!
Making a progressive web app with webpack just got a little bit easier
Today we are releasing webpack-web-app-manifest-plugin, which generates an app manifest that shows up in your assets manifest.
I heard you like manifests
Turns out, there are a lot of web things called “manifests”. When talking about web app manifests and assets manifests, sometimes it’s hard to keep track. Buckle up, because we made a webpack plugin that deals with both of these types of manifests.
Web app manifests are JSON files that allow your application to specify the way it should be treated when installed as an application on a mobile device. You may want to specify what the application name and icon should be. Maybe you want to tell the browser to tint some of its UI elements to match the color scheme of your page, or even hide the browser chrome entirely. You can do all of that with a web app manifest.
Assets manifests are JSON files that contain paths to assets that are generated by webpack. They’re generated by plugins such as assets-webpack-plugin. If you add hashes to the end of your filenames to allow cache busting, assets manifests can be very useful. For example, we use our assets manifest to add JavaScript and CSS files to our <script> and <link> tags.
So I put a manifest in your manifest
While we were building our web app manifest, we wanted to be able to add a hash to the file path and <link> to it. So we needed to add it to our assets manifest. Unfortunately, we were unable to find any existing open-source plugins that output the file in the correct way to add it to the app manifest. So, we built webpack-web-app-manifest-plugin.
By default, webpack-web-app-manifest-plugin assumes that you will name your icon files in the format manifest/icon_[square dimension].(png|jpeg|jpg). If you name them using that scheme, you can use this plugin just like this:
// in your webpack config import AppManifestPlugin from‘webpack-web-app-manifest-plugin’;
// in your page template const manifest = // however you usually access your asset manifest in code const appManifestPath = manifest['app-manifest’].json;
<linkrel=“manifest”href={appManifestPath} />
If you named your icons with some other naming scheme, you can still add them to the web app manifest, it’s just a little more work. That process is detailed in the README.
Please use it
We’re really proud of the work we’ve done to make web app manifests compatible with asset manifests, which is why we’ve decided to open source it and publish it on npm. Please use it.
If this plugin doesn’t meet your needs, we welcome pull requests. And if you have a passion for progressive web applications, webpack, and open source, join our team!
We just published v1.1.0 of the tumblr.js API client. We didn’t make too much of a fuss when we released a bigger update in May, but here’s a quick run-down of the bigger updates you may have missed if you haven’t looked at the JS client in a while:
Method names on the API are named more consistently. For example, blogInfo and blogPosts and blogFollowers rather than blogInfo and posts and followers.
Customizable API baseUrl. We use this internally when we’re testing new API features during development, and it’s super convenient.
data64 support, which is handy for those times when you have a base64-encoded image just lying around and you want to post it to Tumblr.
Support for Promise objects. It’s way more convenient, if you ask me. Regular callbacks are still supported too.
Linting! We’ve been using eslint internally for a while, so we decided to go for it here too. We’re linting in addition to running mocha tests on pull requests.
Check it out on GitHub and/or npm and star it, if you feel so inclined.
tumblr.js REPL
When we were updating the API client, we were pleasantly suprised to discover a REPL in the codebase. If you don’t know, that’s basically a command-line console that you can use to make API requests and examine the responses. We dusted it off and decided to give it its own repository. It’s also on npm.
If you’re interested in exploring the Tumblr API, but don’t have a particular project in mind yet, it’s a great way to get your feet wet. Try it out!
Having an official JavaScript client library for the Tumblr API means that you can interact with Tumblr in wild and wonderful ways. And we know as well as anybody how important it is to foster that kind of creativity.
Moving forward, this kind of creativity is something we’re committed to supporting. We’d love to hear about how you’re using it to build cool stuff here on Tumblr!