HN user

alayek

118 karma
Posts2
Comments33
View on HN

Regarding rem <-> px conversions, I follow this intuition: _multiply by 4 gives you px value, dividing by 4 gives you rem value._

`h-8` utility is height of 8 * 4 = 32px or 8 / 4 = 2 rem.

Since design systems are supposed to be consistent, 4px is typically considered 1 unit of distance.

However, there are times when I might encounter values in Figma (or get inputs from designer) that won't allow me to use this easily.

Say, designer wants a max-width of 252px on an element. I usually use Alfred app on my work MBP to divide it quickly by 16 (since 1 rem is 16px under normal font-size settings), but you can use any calculator, even the one in Google search or DDG search.

It turns out to be a fraction, and in this case, it's 15.75 rem.

I use utility like `max-w-[16rem]`, closest consistent dimension that's a multiple of 1rem, and ship a pull request preview to the designer, asking for design feedback.

Chances are, designer agrees to stick to 16rem, and we ship it as is. If this width of 16rem, or closer values within the 250px vicinity, are used in other places in the design system components or our app; I'd typically add that to the Tailwind config as well.

Most of the time question of rem <-> px conversion comes into picture because we look at design dimensions in Figma / Sketch / Indesign etc. tools, and try to implement the same in our UI code.

But this would only slow down a developer, switching back-and-forth between design and implementation.

What I find more productive while prototyping a UI (or a smaller component), is to just "eye it", instead of getting actual pixel-values right at the first go.

From just eye-ing it, I can make a guess if it should be h-3 or h-4 (you can also guess the right value using a binary search style heuristic), and if my implementation looks bigger (or smaller) than the design, I'd adjust accordingly.

Only after I've implemented a basic prototype of the UI component, I'd cross-check with the design tool, and edit some utilities if necessary to get as close to the design as possible.

At work, we've built a component library styling readily available headless components from Radix UI, Headless UI, Reach UI etc. with a customized Tailwind CSS preset.

We had to use `rtl:` directive only in one component, where we were animating translation property, of a switch toggle. `translation-x-` is RTL specific, so we've to apply negative translation if `dir="rtl"` has been set.

Otherwise, this symmetric-utilities-only approach worked really well across dropdowns, modals, tooltips, buttons, inputs etc., even when we integrated these components in different existing products across multiple teams.

I hope one day Tailwind CSS team release an update with underlying CSS for px-, mx-* being replaced with CSS logical operators for corresponding properties. Not sure where the browser support for the same is currently.

I've had some experience migrating large UI projects at work to use a design system based on Tailwind. Haven't faced any issue with Tailwind at all, even while integrating in existing projects.

Tailwind is a different way of writing CSS, with some guard-rails coming in from the design system consistency. Tailwind also provides sane defaults (rem over px, for instance).

If someone's bad at writing or thinking in CSS, chances are, they'd be bad at Tailwind too.

I've found Tailwind helps me focus on writing the CSS that matters. But just having Tailwind CSS in a project won't automagically fill one's gap in CSS knowledge.

This isn't bootstrap, where one can use some col-* utilities and magically get a grid layout that just works across browsers.

Tailwind is a bit lower level, and to achieve similar responsive layout, you still have to know how to do that with CSS grid, what the breakpoints are etc.

Where Tailwind aids here, is by providing utility classes that help with original CSS grid intuition. For instance, `grid-cols-2 sm:grid-cols-4 md:grid-cols-6` would be hard to achieve by hand, writing vanilla CSS or styled-components.

A bigger project with multiple pages having full on Tailwind css classes peppered everywhere looks to be a nightmare.

Ideally, you would be using a framework to help organize code better. Most likely React or Vue or something similar.

In which case, you'd already have components.

There are more than one way to achieve same look and feel, with CSS. Similarly, one can apply some discipline with using Tailwind's utility classes, when building anything.

Two heuristics that have worked for me, are:

- Never use margin if you can, use flex-box and grid with gaps instead. Placing a children node is parent's responsibility.

- Write symmetric CSS. Prefer px over pl or pr, mx over ml or mr etc.

Using margins make it hard to extract some React markup as a reusable component. Using symmetric CSS gives you automatic RTL compliance without using any of the CSS logical properties.

Sure, there'd be times when a UI design cannot be implemented without breaking some of these. But in most cases I've encountered at work, building consistent UIs, have been easy for me and my team following these.

Happy to go into details with code examples, if anyone's interested.

I found that one can make Tailwind CSS utility classes appear vertically, if one's been using clsx library[1] (or something similar). clsx accepts an array, and at that point, prettier formatting kicks in.

You could have a React `<Button>` component's styling go like this, with clsx and Tailwind:

``` <Button type="button" className={clsx([ "inline-flex items-center", // how its children nodes should be laid out "px-3 py-2", "bg-gradient-to-r from-blue-500 to-indigo-500", // background color stuff "rounded-md", // border radius "text-white", // text color of children nodes "outline-none hover:ring-4 focus:ring-4 ring-blue-500/40", // hover focus behavior "disabled:hover:ring-0 disabled:cursor-not-allowed" // disabled state "disabled:bg-gradient-to-r disabled:from-blue-400 disabled:to-indigo-400" ])} {...props} > Click Me </Button> ```

Imgur link on how it looks like in the editor: https://imgur.com/r0aF9oU

Here's a similar button component implemented with horizontal utility classes: https://play.tailwindcss.com/5sbxDmVvsZ.

There are other benefits to using a library like clsx. Since clsx accepts array of strings and returns a joined string based on conditional, output of one clsx call can be consumed by another clsx call.

References: - [1]: https://www.npmjs.com/package/clsx

I'm not sure why you believe this would be a difficult task to achieve.

You've already mentioned in the other comment, that drift is your concern, and that can be fixed easily. Though it has nothing to do with hooks. One has to track the time when the clock starts, and query current time on every tick.

As for creating a setInterval() and tearing it down after every render, you can simply pass an empty deps list, instead of declaraing counter variable as a dep in the array. Or, if you want to keep ESLint happy, declare counter as a dep, but use `setTimeout()` instead of `setInterval`

I took a stab at this (React with TS):

  import * as React from "react";
  const TICK_INTERVAL: number = 1000;

  function App() {
    const [counter, setCounter] = React.useState<number>(0);
    React.useEffect(() => {
      const startTime = Date.now();
      const timer = setInterval(() => {
        setCounter(_ => {
          const timeNow = Date.now();
          return Math.round((timeNow - startTime) / TICK_INTERVAL);
        });
      }, TICK_INTERVAL);
      return () => {
        if (timer) {
          clearInterval(timer);
        }
      };
    }, []);
    return (
      <div className="App">
        <h1>{counter}</h1>
      </div>
    );
Codesandbox link with working demo: https://codesandbox.io/s/amazing-solomon-mf3r6

But this code wouldn't be that different if you wrote it in class fashion, except you'd be making a few of calls to this. Most of this would go in componentDidMount and the cleanup would go in componentWillUnMount

Where hooks really shine, is if you want to add / extend this functionality.

Say, you now want the counter to pause when you're not looking at that tab, and resume once the browser tab is in focus. Imagine if you had a pageVisibility API hook, and it returns true and false accordingly, based whether or not the tab is visible at any point of time or not.

In a real-world scenario, it'd not be a basic counter, but maybe an API polling for real time data, and user don't want the page to keep on polling when not in focus.

In that case, you'd have two changes: one call to the useVisible hook, and one more to pass the boolean output of this hook into your Effect hook.

  const TICK_INTERVAL: number = 1000;

  function App() {
    const [counter, setCounter] = React.useState<number>(0);
    const startTime = React.useRef<ReturnType<typeof Date.now>>(Date.now());
    const isVisible: boolean = useVisibility(); // assume taken from NPM
    React.useEffect(() => {
      const timer = setInterval(
        () => {
          if (TICK_INTERVAL && isVisible) {
            setCounter(_ => {
              const timeNow = Date.now();
              return Math.round((timeNow - startTime.current) / TICK_INTERVAL);
            });
          }
        },
        isVisible ? TICK_INTERVAL : null
      );
      return () => {
        if (timer) {
          clearInterval(timer);
        }
      };
    }, [isVisible]);
    return (
      <div className="App">
        <h1>{counter}</h1>
      </div>
    );
  }
Working codesandbox demo: https://codesandbox.io/s/heuristic-mcnulty-oo58z

Now, let's go one step further, and add local-storage / indexed DB for storing these values, on page close. If you close the page, and re-open, it should resume from where it was before closing, and then count up from there - not zero.

All we need now, is another hook, that abstracts away that storage interaction, storing on window.unload and componentWillUnMount, and retrieving value from storage when component mounts for the first time.

After this point, it'd be illuminating to look back and try to combine these three functionalities, using class components with HOC or Render Props pattern; and think about the effort it'd take to decouple and reuse the count-up logic, from the page visibility logic, from the storage logic.

Edit: formatting

In India, Spotify Premium is 119 INR / month, while Apple Music is 120 INR / month. Spotify also have few other options, like annual membership, and student plans.

While recommendation is off the charts, the collection simply isn't there. Mostly due to licensing issues, I assume.

For instance, really loved the tracks from Suits, and got my own playlist on YouTube too, consisting off some nice tracks compiled from tunefinder.

But the Spotify track list is only a subset, and not all those tracks are available.

Am on iOS, so no Google Pay Music on phone, and for me Apple Music is no worse than Spotify, except when it comes to search.

I learned this the hard way, coming from React to Vue land; that v-for on an element would also repeat the element itself.

With React, it was pretty clear which element / component would repeat.

Have been developing with React for last 3 years, and recently started building with Vue JS.

React's core philosophy is JS everywhere - there's no template, no HTML, no CSS; only JS. As much as possible, write JS, and if you can, pure JS.

It has its benefits - you can have code formatter to format your JSX, unit test any part of your code, or write integration tests with mount, or write snapshot tests. Extremely easy and natural to compose various components - because it's a function calling another function.

Vue is magic, and its own DSL rules will get in your way. For instance, inside the Vue templates you cannot use `this`. However, inside your methods and computed, you have to.

Coming from Angular 1, Vue JS would look feel like a breeze.

Never could make ESLint work on my Vue files, tried all plugins. Some parts of the code it lints, and some parts of the code it doesn't.

Inside your template, Vue.config can be undefined; so you've to assign it to something in mounted(), and then get it to work.

Vue artisans tell me Vue supports "this" or "that". I don't want something that supports A or B - I want something where A and B are inherently supported from the ground up.

It's not all bad. VueX is pretty cool, kinda like Redux without the drama. And I cannot stress this enough - it's insanely easy to go through a Vue codebase and pick up the business logic of your app.

It still takes me some time to get used to a new React codebase.

Overall, I felt Vue is really good to build MVP and get your product off the ground, but it starts to show why you need React's discipline in your codebase with growing requests from users to add new features and support more platforms.

Then again, I'm new to Vue JS; so ask me again in a year. Am keeping an open mind.

Have used Buildkite at a previous workplace. Really liked the UI. This is coming from someone who has used Jenkins, Travis, Codeship, and GitLab CI.

Their front-end is open source: https://github.com/buildkite/frontend

However, if you add your own CI runners, you'd have to clean them up periodically. This was the biggest issue for us, because even after cleaning them up from all docker images once a week, builds would get stuck.

Node.js v9.2.0 9 years ago

This is an apple to orange comparison.

Gulp is a tool that lets you decide what the build process should look like.

Webpack is a tool where you define entry points, split points etc., and what you finally need. Based on that webpack decides how to go about the build.

Most of what Gulp does, can be replaced with NPM scripts.

You'd be surprised.

I have a friend, whose team had this policy - no raise with promotion.

He got a promotion with no raise. When asked, his manager said, "you shouldn't work for money. You should work for the learning experience."

He resigned in a month. And this was not some random fringe start-up - one of the biggest companies in the world, with over $100B in market cap.

In my first job, about four years ago, my first task was to make certain modifications in firebug for our company's internal use (mostly in reporting and filtering).

I didn't even know JS to begin with. Jan Orvadko, the lead maintainer of Firebug, is an amazingly approachable & affable human being. He helped me a lot in the IRC channel, in going through the firebug codebase.

I was confused with this initially as well.

What I came to understand, was that "server-side rendering" refers to whole application lifecycle steps: - The server generates initial HTML markup, based on the request. - The browser can parse & render the already generated HTML (instead of waiting for the client-side JS to load first, then have it create / modify the DOM). - From that point onward, all changes in UI happen through client-side rendering.

You could do this with Django template / JSP pages / Jade templates as well. Just write your templates, and some jQuery sprinkled here and there, to dictate how user interaction should change the page.

But that's not all there is to it.

We slowly took away SSR, with client side JS frameworks, like Backbone, Angular 1, Ember etc. Applications started to have client side routes, even with ugly hashbangs in the URLs.

It was easier to write entire application logic of your UI in a client-side framework with two-way data bindings; than writing the server side in a template language, and put in some JS here & there to handle interactions. Especially, if your application was big enough.

But this started having problems with SEO, and initial load times.

Say, your app has a homepage URL, of the form https://company.tld/, and a products URL, of the form https://company.tld/products.

If a user goes to homepage, and clicks on the "products" link in the navbar, the page content of /products route would load in the client side, via the framework.

But, if a user directly hits the /products URL from their browser (can be from history and omnibox prompt), the browser would first load the home page with assets, then the client side JS would take over, and route the user to the /products URI endpoint.

New server-side rendering paradigm gives you best of both worlds - get to maintain a single codebase to render your page (be it server or client), while give the initial fast loading, so that your page is interactive quite fast.

This is not easy to do. One big challenge for most server side rendering solution is that, after initial render, the JS in the page would try to re-render the page; thereby creating an impression of a "flash".

Yes, if you're using a framework like React or Vue, which uses VDOM, this won't happen in most cases, because of how a virtual DOM based renderer works. But there are still cases, where you might need to explicitly do something to prevent the re-render.

There are other challenges too. For instance, having the page server-rendered, and hydrating the state.

In the search of maintaining a single unifying codebase for both server and client (isomorphic rendering), we often do things that cannot work on server. For instance, chunk splitting your front-end bundle based on routes. Or CSS media queries.

To sum it all up, "server-side rendering" isn't just about initially rendering the page on server. It's so much more than that. It's mostly about how we can maintain a single codebase (now that we have JS on server too), and run on two different platforms.

instead of the current situation where they are copied and then made mutable.

Could you share an example? As far as I understand, Immutable JS uses something like a trie for structural sharing and avoids memory leaks.

We still don't have a type system, static analysis, multi-threading, first-class IDE support

Let me try to address these.

Regarding type system, lot of great languages don't have type systems either. But if there's lot of discussion on type systems, it would be because of Flow vs TypeScript.

As for static analysis, JS has seen evolution of JSHint, JSLint, JSCS, ESLint, and finally, Prettier. Flow is capable of performing type-checks with dynamic analysis.

I am not sure multi-threading is the hallmark of a modern language. Because Go, Elixir etc. are providing great concurrency without exposing threading API to the developer. JavaScript lets you do asynchronous tasks via callback, promises, generators, and latest - async/await. In the browser, you can use web-worker. But in most cases, you won't need it.

JS ecosystem has great code editors. I find VSCode to have good support out of the box, with breakpoint debugging. You can also use Atom, or Sublime Text, or Webstorm. If you're missing certain functionality, you can always add open-source plugins.

Is nobody even slightly wondering how OOP has infiltrated modern JS development?

I would like you to give an example of this. We use the class-syntax, yes, but inheritance usually never goes beyond one level of parent-class-child-class relationship.

If anything, the JS community at large hates OOP the traditional way, and somewhat open to the prototypical pattern.

The ecosystem actually prefers functional constructs. At the very least, an attempt to write pure functions.

Even better. You don't even need to set up a build system for async/await, ES6 modules (coming in Chrome 61) these days. Most modern browsers support a great portion of ES6+ specifications by default.

Live / Hot reloading might take some setup though.

One thing I am yet to see implemented, is tail-recursion. It's in the spec, but I am yet to find an environment that implements it. Please correct me if I'm wrong.

A refreshing read. Was expecting a satirical negative take on the complexity.

However, this only covers a small part of front-end development with VDOM based approach, and a few ES6 constructs (destructuring, arrow functions, let-const).

The author misses out on mobile development, Node JS and NPM modules, server side, and Electron-based desktop apps.

It's a good thing to take the positive approach, and get introduced to some interesting, low-barrier-to-entry items. At the same time, it would be prudent to keep in mind what lies ahead.

The way I see it, there are two types of callbacks - synchronous and asynchronous.

This is a synchronous callback (but callback nonetheless), that takes an array of numbers, and adds its entries:

arr.reduce(function(acc, item){ return acc + item; });

There's some boilerplate in this, so as per latest JS specs, you can remove all that and rewrite it as:

arr.reduce( (x, y) => x + y);

Then there's asynchronous callback. Most of "callback-hell" stems from these type of callbacks, because you would do "do-this-then-do-this-then-the-other-thing", and nest these one within other.

We realized that callback-hell prevents us from using return or handle errors in a clean & reliable way, so we went to promises.

Then promises turned out to be quite a pain too. Then generators, and now we have settled for async-await.

But in no way that "hell" is acceptable!

I have been in your position about a year ago. The approached that helped me, was to not worry about all this.

Just start somewhere; and in a month or two, you would mostly know which libraries you need to add to your project.

The best way to go about it, is to check a few popular open source projects; what libraries they typically use.

I am also asking if any of those libraries will be around in a year?

You can check the download stats of these in https://npmjs.com, and activity on the GitHub repo, before adding it to your project.

If they are still around, but have published a new major version to update a few APIs; you can slowly migrate to it.

If not, you'd read some Medium post or Hackernews discussion, about what is replacing that.

There's something called Greenkeeper (https://greenkeeper.io/) that integrates a GitHub bot to your repo, and files a PR when something needs to be upgraded.

You can implement something similar on your own, if you aren't interested in using something like this.

The way to look at it, is that browser is a target runtime platform.

When you write code in a team, you optimize for readability, maintainability etc. You spread your code over 5 directories, 50 subdirectories, 3000 files.

When you want to run this in a browser environment, it needs to be optimized for delivery - faster download, less time-to-be-interactive. You need to split your JS codebase as per routes in your app, and load chunks as per user demand.

Not just JS, you'd have to compile and inline your CSS, base-64 encode your images to data-urls, generate image stripes.

Before all these, you'd have to lint your code, and run unit tests on your code.

I fail to see how you can do all these with just a script tag. You need some sort of a compiler / transpiler; and a task runner that gives you a handy interface to these.

The package manager has no dependency, other than the Node runtime itself.

As for arbitrary toolchain, the metadata is codified with semantic versions in your package.json. You're one install step away from downloading all the necessary packages.

One thing I think lot of Node project misses, is to not add which Node version is to be used. It's not because it's impossible to do that - there are two well documented ways to do that (via .nvmrc and "engines" entry in package.json)

I didn't know that, thanks for pointing out.

I like Yarn run, because if I'm running a lint or tests, it errors out with error messages related to the specific process.

If you run via npm run, it would throw lot of extra error messages, making excuses that it wasn't NPM's fault the command exited with a non-zero status etc.; and hiding the relevant error messages.

And as always, you can just run a script as yarn script, instead of yarn run script; like yarn lint:src, instead of yarn run lint:src.

NPM does it only for a few common ones.

Also, Yarn run picks up right node version from .nvmrc - you don't have to run an nvm use and switch to it.

Personal experience: NPM can be mind-bogglingly slow in Windows. In my previous day-job, an NPM install on Windows 7 could take hours, for some reason. That's NPM 3 with about 20 dependencies on a small project.

NPM on Windows also had other problems such as Python path is wrong or C++ compiler is missing.

Then we switched to Yarn. Uncached install took about 2-3 minutes, and cached install would finish in seconds.

Recently, NPM 5 has been released, and it's been competing well against Yarn.

I have never claimed that it's unique. In fact, most ideas in tech is just old wine in a new bottle.

However, I guess what made it appealing initially, was that it uses a syntax known to a lot of web developers. The callback pattern helped.

Although these days, we have been moving away to generators or async-await.

Sorry to say this, but this kind of statements don't really add anything to the discussion. _Only a sith deals in absolutes_.

Recently, JavaScript has adopted lot of language feature from C#, Ruby etc., that make it really useful for most common tasks. And the community moves really really fast, hence adoption of new language syntax is quite fast.

JavaScript has some killer platforms / apps, that are being used in production by high-traffic companies.

You can do cross-platform Desktop app in Electron (Slack, VSCode, Atom etc. are written with this).

Front-end build tools are written in Node JS, and they do lot of heavy lifting as well. Babel is a transpiler, Webpack is a bundler.

These days, native mobile apps (not hybrid apps) in JS are also gaining traction, via React Native.

From open source communities to big behemoths are behind this.

I would like to hear what about JS turned you off so much?

You should try. I come from a traditional Java shop, and a year ago wouldn't have thought of doing anything serious with Node JS.

Today, I do front-end React stuff as well as back-end servers with Node JS. I have been happy with it so far.

With some type-system like Flow or TypeScript, you can manage huge projects in Node JS.

I happen to like the community, CLI tools written in Node, and the plethora of open source libraries built on top of Node.

This also has a downside. Your libraries can get outdated quite fast, you would have to keep up with new language syntaxes etc., and remember which syntax is supported by which version of Node etc.

But so far, I think Node is headed in the right direction.