Coder Social home page Coder Social logo

blog's People

Contributors

lydell avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

blog's Issues

Adding blog posts

TL;DR If you can update a blog really easily, you’ve won. Name your posts #1, #2 and #3-whatever.

The problem

Let’s say you’re authoring some blog that has a lot of posts in it. In particular, this post: #2. Every time you read that post, you simply read color: #42aad5;, background-color: #42aad5;, box-shadow: 0 0 5px #42aad5; or whatever. So far so good.

Then one day, a reader says to you: “You know that blog with color codes all over the place? Can you add a post to it, so that it ‘pops’ a little more?”

Now you have plenty of changes to make. Easy enough, though. Just create a new post with ‘popping’ content.

But shortly after your change, the reader comes to you again. “The new post does not seem to show up on the blog, could you take a look at what’s going on?”

Turns out the path had been written uppercase in the database for some reason: /post/POPPING. Better remember to do a case insensitve path routing next time.

But it doesn’t end there. If Unicode has been involved during the session, ever-so-slightly-off variations of the path might exist. Perhaps your reader wrote pöpping instead of popping. Nobody’s eyes can tell the difference, but it’s enough to fool your routing.

A solution

Use a VCS blog platform. I like GitHub. You can also use GitLab, SourceForge, Bitbucket with some plugin, or a custom repository on a modern server, but I’ll go with GitHub in this post.

In lots of blogs I’ve worked on, there’s an issues page containing global posts for, well, global stuff. package.json files, npm warnings, some Star Wars stuff–and, most importantly, naming CSS colors.

With issues for posts, the popping update would have been super easy:

new

Teach your readers to look for posts in issues instead of typing in their paths and you should be fine.

Conclusion

Blogs are important. Make them easy to use. Easy to change. Easy to talk about with other people. Don’t make blogging hard for yourself. Be practical. And allow a little fun in your blog. :)

How to create a package.json – the Right Way™

Lots of guides on the Internet simply tell you to run npm init. But really, first you should be asking yourself a question:

Are you going to publish your project on the npm registry?

  • Yes: npm init
  • No: echo '{"private": true}' > package.json

Why

package.json files are used for two things. They define the name, version, description and so on for packages that can be installed from the npm registry. They are also used for various configuration in all sorts of JavaScript projects.

Let's start with npm packages. In this case, the name and version fields are required to be able to publish:

$ echo '{}' > package.json
$ npm publish
npm ERR! package.json requires a "name" field

$ echo '{"name": "mywebsite.com"}' > package.json
npm ERR! package.json requires a valid "version" field

$ echo '{"name": "mywebsite.com", "version": "1.2.3"}' > package.json
$ npm publish
+ [email protected]
# (Ok, I didn't actually publish that for this demo.)

Running npm init will prompt you for name and version as well as other useful fields, making it easy to get started with your new npm package. Not much to say there. The interesting case is when you aren't making an npm package.

Let's say you're making a website and use npm to install jQuery.

If you start out using npm init you'll end up with a package.json valid for publication. So when you are multi-tasking and accidentally run npm publish in the wrong terminal window, you have suddenly made your entire website source code available publicly on the Internet.

"private": true to the rescue! Adding this to package.json will make npm refuse to publish:

$ echo '{"name": "mywebsite.com", "version": "1.2.3", "private": true}' > package.json
$ npm publish
npm ERR! This package has been marked as private
npm ERR! Remove the 'private' field from the package.json to publish it.

Most likely you have absolutely no need to specify name and version (as well as description, author and license) in package.json for a website, so you could just as well remove them.

If we remove name and version, doesn't that make private redundant as well? As seen above, npm will refuse to publish if those fields are missing anyway. The answer is: No. Here's why:

echo '{}' > package.json
$ npm install jquery
npm WARN package No description
npm WARN package No repository field.
npm WARN package No license field.

+ [email protected]
added 1 package in 0.806s

See those warnings? npm is going to nag you about missing fields every time you install something.

I guess it's a good thing that npm bugs package developers about this frequently, since those fields are very useful. But not for a website. Luckily, npm understands this and doesn't nag you if you tell it that you won't publish your project. Like adding that private field.

$ echo '{"private": true}' > package.json
$ npm install jquery
+ [email protected]
added 1 package in 0.805s

No more warnings!

Conclusion

For npm packages, npm init is a great way to guide you through the needed fields.

In all other cases, "private": true is all you need upfront. No unused cruft in package.json, and no risk of accidental npm publish.

Note: This was tested with npm 5.3.0.

Import/export, React and Rollup

🔗 Link to a gist containing the files used in this post.

You know how you can import most React stuff two ways?

import React from "react";

function MyComponent() {
  const [count, setCount] = React.useState(0);
  // ...
}

Or:

import { useState } from "react";

function MyComponent() {
  const [count, setCount] = useState(0);
  // ...
}

There’s kind of a third way as well:

import * as React from "react";

function MyComponent() {
  const [count, setCount] = React.useState(0);
  // ...
}

What’s the difference?

The short answer: In the case of React as of today: No difference at all.

The long answer: The devil is in the details.

First, let’s review how import/export works. In a nutshell, if you export something in one file, you can import that thing in another file.

// constants.js
export const NAV_HEIGHT = 80;
// main.js
import { NAV_HEIGHT } from "./constants.js";

Then there’s this thing called “default exports”. Every file can have one one default export if it wants to. There’s basically two ways of defining them:

// add.js
export default function add(a, b) {
  return a + b;
}

Or:

// subtract.js
function subtract(a, b) {
  return a - b;
}
export { subtract as default };

There are two ways of importing default exports as well:

// main.js
import add from "./add.js";
import { default as subtract } from "./subtract.js";

It doesn’t matter how you defined you default export – you can choose either import style:

// main.js
import { default as add } from "./add.js";
import subtract from "./subtract.js";

Also note that you can choose other names for default imports if you want:

// main.js
import { default as plus } from "./add.js";
import minus from "./subtract.js";

Alright, with that out of our way we can think about what React could look like.

// Support `import { useState } from "react"`;
export function useState() {
  // whatever
}

const React = {
  useState
  // and lots of other things
};

// Support `import React from "react"; React.useState`.
export default React;

Oh – I forgot about import * as React from "react". It imports all exports from a file into an object. With the above implementation of React you’d get this:

import * as React from "react";

console.log(React);
// {
//   useState: function useState() {},
//   default: {
//     useState: function useState() {},
//     // and lots of other things
//   }
// }

Let’s have a look at what’s actually in the react npm package. Check it out:

https://unpkg.com/[email protected]/cjs/react.development.js

Search for export in that file. What do you find? Three matches in comments, and this:

module.exports = react;

What’s this? It’s Node.js’ style of exporting stuff from files. This is also sometimes called CommonJS. (There are some differences between the Node.js and CommonJS systems, but let’s not get into that.)

Node.js’ module system has been around for much longer than import/export so it’s still in a lot of packages on npm.

So how could import React, { useState } from "react" possibly work? If you play around with import/export in browsers you’ll see that it doesn’t.

// test.js
// No `export` here!
<!DOCTYPE html>
<meta charset="utf-8" />
<title>import/export test</title>
<script type="module">
  import whatever from "./test.js";
  // In the console:
  // SyntaxError: import not found: default
</script>

The reason import React, { useState } from "react" usually works anyway is because of webpack. I like to think of it as if webpack rewrites module.exports to export syntax, allowing you to import from it. But just how does it do the rewriting?

A very simple way would be:

export default module.exports;

That very closely resembles how Node.js’ module system works: You can only export one value – whatever you set module.exports to. But this means that there won’t be any named exports. So bundlers started doing this:

export default module.exports;
const { named1, named2, named3 } = module.exports;
export { named1, named2, named3 };

Because in the Node.js world it is a common pattern to set module.exports to a function (the main export), and then add some extra properties to that function (secondary exports). For example, express does this.

function express() {
  // creates you express server
}

function static() {
  // helper that serves static files
}

express.static = static;
module.exports = express;
// your-server.js
const express = require("express");

const app = express();
app.use(express.static("public"));

If you use a bundler or Babel for your server-side code, you can now do:

import express, { static } from "express";

const app = express();
app.use(static("public"));

However, since bundlers and Babel added support for import/export before browsers did, lots of people learned import/export from using their bundler, and got the impression that named exports always work this way.

// constants_node.js
module.exports = {
  NAV_HEIGHT: 80,
  TIMEOUT: 2500
};
// constants_broken.js
export default {
  NAV_HEIGHT: 80,
  TIMEOUT: 2500
};
// constants_correct.js
export const NAV_HEIGHT = 80;
export const TIMEOUT = 2500;
// main.js

// Works because of your bundler:
import { NAV_HEIGHT, TIMEOUT } from "./constants_node.js";

// Error: `{ NAV_HEIGHT, TIMEOUT }` does not destructure the default export!
// It looks for named exports called `NAV_HEIGHT` and `TIMEOUT`, which don’t exist.
import { NAV_HEIGHT, TIMEOUT } from "./constants_broken.js";

// This is OK:
import constants from "./constants_broken.js";
// This (confusingly?) works too:
import constants from "./constants_node.js";

// This works because constants_correct.js does define these exports:
import { NAV_HEIGHT, TIMEOUT } from "./constants_correct.js";

// But this does not work because constants_correct.js does not have a default export.
import constants from "./constants_correct.js";

// Finally, you can import all the exports as an object if you want to:
import * as constants from "./constants_correct.js";

To avoid this confusion, there is another way to think of how module.exports should be turned into export syntax. The idea is that module.exports is the object you get (all) when you do import * as all from "something" (or const all = await import("something")). This way you do get named exports as well.

// This is exactly the same as before, but with `.default` added here:
export default module.exports.default;
const { named1, named2, named3 } = module.exports;
export { named1, named2, named3 };

However, Node.js packages typically don’t expose anything called default. But newer packages are sometimes written with import/export syntax and transpiled to module.exports before shipping to npm. For example, express could have been written like this:

export default function express() {
  // whatever
}

export function static() {
  // whatever
}

Before shipping to npm, it could be transpiled to:

module.exports = {
  default: function express() {
    // whatever
  },
  static: function static() {
    // whatever
  }
};

So how do bundlers know what to do when you write import express from "express"? Should express be set to module.exports.default (as in the above example), or to module.exports (as in the original example, where module.exports.default would be undefined)? The solution to this problem is the (non-standard) __esModule property. The above example would actually be transpiled to this:

module.exports = {
  __esModule: true,
  default: function express() {
    // whatever
  },
  static: function static() {
    // whatever
  }
};

When you then do import express from "express", bundlers inject a little piece of code to determine what to pick. It essentially works like this:

var express = obj.__esModule ? obj.default : obj;

Babel’s noInterop documentation phrases it well:

[an] __esModule property is exported. This property is then used to determine if the import is the default export or if it contains the default export.

So, what has all of this to do with React? Well, as shown earlier, the code that is shipped to npm contains:

module.exports = react;

And if you look where the react variable comes from, it is essentially set to this object:

var React = {
  Children: {
    map: mapChildren,
    forEach: forEachChildren,
    count: countChildren,
    toArray: toArray,
    only: onlyChild
  },

  createRef: createRef,
  Component: Component,
  PureComponent: PureComponent,

  createContext: createContext,
  forwardRef: forwardRef,
  lazy: lazy,
  memo: memo,

  useCallback: useCallback,
  useContext: useContext,
  useEffect: useEffect,
  useImperativeHandle: useImperativeHandle,
  useDebugValue: useDebugValue,
  useLayoutEffect: useLayoutEffect,
  useMemo: useMemo,
  useReducer: useReducer,
  useRef: useRef,
  useState: useState,

  Fragment: REACT_FRAGMENT_TYPE,
  StrictMode: REACT_STRICT_MODE_TYPE,
  Suspense: REACT_SUSPENSE_TYPE,

  createElement: createElementWithValidation,
  cloneElement: cloneElementWithValidation,
  createFactory: createFactoryWithValidation,
  isValidElement: isValidElement,

  version: ReactVersion,

  unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
  unstable_Profiler: REACT_PROFILER_TYPE,

  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals
};

And that object does not have __esModule: true. So when you write this:

import React, { useState } from "react";

Your bundler effectively turns it into:

import React from "react";
const { useState } = React;

(Remember: { useState } in the import line does not mean destructuring of the default export in standard JavaScript – only in bundlers with Node.js-support world!).

That’s why the short answer to my initial question (what’s the difference between import React from "react"; React.useState and import { useState } from "react") is:

In the case of React as of today: No difference at all.

But there is an interesting quirk to it. After all of this talk about import/export and Node.js/CommonJS interoperability I can finally come to the thing that made me think about this recently.

Kent C. Dodds recently tweeted about a nice little React library he had made: stop-runaway-react-effects. It monkey-patches React’s useEffect (and useLayoutEffect) with versions that warn you about accidental infintie loops. Basically, the library does this:

import React from "react";

const originalUseEffect = React.useEffect;

React.useEffect = (callback, deps) => {
  return originalUseEffect(() => {
    // do stuff to detect infinte loops
    return callback();
  }, deps);
};

So the next time you run this:

React.useEffect(() => {
  // whatever
}, []);

The infinite loop detection is automatically run for you. But what if you prefer importing useEffect separately?

import { useEffect } from "react";

useEffect(() => {
  // whatever
}, []);

As we’ve learned above, webpack basically does this behind the scenes (in the case of React):

import React from "react";
const { useEffect } = React;

useEffect(() => {
  // whatever
}, []);

And if you’ve run the monkey-patch before that, the React object has already been mutated, explaining why it works for this import style too.

But. And here’s the big but. This relies on your bundler actually running const { useEffect } = React; after the monkey-patching has run. webpack does so (check its compiled output!). But there’s nothing saying it has to. Rollup is an example of this. Let’s see how it compiles stuff.

// main.js
import "./monkey-patch.js";
import React, { useEffect } from "react";
import ReactDOM from "react-dom";

function App() {
  useEffect(() => {
    console.log("My useEffect code is running.");
  }, []);
  return "test";
}

ReactDOM.render(React.createElement(App), document.getElementById("root"));
// monkey-patch.js
import React from "react";

const originalUseEffect = React.useEffect;

React.useEffect = (callback, deps) => {
  return originalUseEffect(() => {
    console.log("Monkey!");
    return callback();
  }, deps);
};
❯ echo '{"private": true}' > package.json

❯ npm i rollup react react-dom
npm notice created a lockfile as package-lock.json. You should commit this file.
+ [email protected]
+ [email protected]
+ [email protected]
added 12 packages from 44 contributors and audited 30 packages in 1.226s
found 0 vulnerabilities

❯ rollup --format=esm main.js
main.js → stdout...
import React, { useEffect } from 'react';
import { render } from 'react-dom';

const originalUseEffect = React.useEffect;

React.useEffect = (callback, deps) => {
  return originalUseEffect(() => {
    console.log("Monkey!");
    return callback();
  }, deps);
};

function App() {
  useEffect(() => {
    console.log("My useEffect code is running.");
  }, []);
  return "test";
}

render(App, document.getElementById("root"));
(!) Unresolved dependencies
https://rollupjs.org/guide/en#warning-treating-module-as-external-dependency
react (imported by main.js, monkey-patch.js)
react-dom (imported by main.js)
created stdout in 54ms

As you can see above, Rollup left the React imports intact because it couldn’t find them. This is because by default, Rollup assumes that your code only uses stuff from the web standards. But we can install a plugin to teach it about Node.js’ convention of looking for modules in the node_modules/ folder.

❯ npm i rollup-plugin-node-resolve
+ [email protected]
added 118 packages from 102 contributors and audited 939 packages in 3.787s
found 0 vulnerabilities
// rollup.config.js
import resolve from "rollup-plugin-node-resolve";

export default {
  plugins: [resolve()]
};
❯ rollup --config --format=esm main.js
main.js → stdout...
[!] Error: 'default' is not exported by node_modules/react/index.js
https://rollupjs.org/guide/en#error-name-is-not-exported-by-module-
monkey-patch.js (1:7)
1: import React from "react";
          ^
2:
3: const originalUseEffect = React.useEffect;
Error: 'default' is not exported by node_modules/react/index.js

Now, Rollup has found its way to the files of React, but it cannot find any export default there. Which makes sense. We’ve already looked at the code React ships to npm and observed that it does not contain export.

Lucklily, we can install a plugin to teach Rollup about Node.js/CommonJS modules:

❯ npm i rollup-plugin-commonjs
+ [email protected]
added 4 packages from 1 contributor and audited 1849 packages in 2.479s
found 0 vulnerabilities
// rollup.config.js
import resolve from "rollup-plugin-node-resolve";
import commonjs from "rollup-plugin-commonjs";

export default {
  plugins: [resolve(), commonjs()]
};
❯ rollup --config --format=esm main.js
main.js → stdout...
[!] Error: 'useEffect' is not exported by node_modules/react/index.js
https://rollupjs.org/guide/en#error-name-is-not-exported-by-module-
main.js (2:9)
1: import "./monkey-patch.js";
2: import { useEffect } from "react";
            ^
3: import { render } from "react-dom";
Error: 'useEffect' is not exported by node_modules/react/index.js

The CommonJS plugin has support for trying to figure out which exports actually exists, but couldn’t find useEffect in React’s code. So we need to give it a hint:

// rollup.config.js
import resolve from "rollup-plugin-node-resolve";
import commonjs from "rollup-plugin-commonjs";

export default {
  plugins: [
    resolve(),
    commonjs({
      namedImports: {
        react: ["useEffect"]
      }
    })
  ]
};
❯ rollup --config --format=esm main.js > bundle.js
main.js → stdout...
created stdout in 2.8s

Now it succeeds without warnings. Here are the interesting parts of bundle.js (slightly simplified for brevity):

// Helper function made by Rollup:
function createCommonjsModule(fn, module) {
  return (module = { exports: {} }), fn(module, module.exports), module.exports;
}

// The entire react code:
var react = createCommonjsModule(function(module) {
  // Lots of code.
  var react = {
    // Lots of things.
  };
  module.exports = react;
});

// Here comes `import { useEffect } from "react"` that we wrote in main.js.
// Notice that this appears _before_ the monkey-patch.js code!
var react_1 = react.useEffect;

// monkey-patch.js:
const originalUseEffect = react.useEffect;
react.useEffect = (callback, deps) => {
  return originalUseEffect(() => {
    console.log("Monkey!");
    return callback();
  }, deps);
};

// The entire react-dom code:
var reactDom = createCommonjsModule(function(module) {
  // Lots of code.
});

// main.js:
function App() {
  react_1(() => {
    console.log("My useEffect code is running.");
  }, []);
  return "test";
}
reactDom.render(App, document.getElementById("root"));

Let’s make an HTML file and run this!

<!DOCTYPE html>
<meta charset="utf-8" />
<title>Rollup test</title>
<div id="root"></div>
<script src="bundle.js"></script>

The browser console tells us this:

ReferenceError: process is not defined

This is because React uses the convention of putting if (process.env.NODE_ENV !== "production") { checks around development-only code, to make production bundles smaller. But there is no process variable in the browser. This is fixed by adding a Rollup plugin that replaces process.env.NODE_ENV with "development" (in production you’d want to replace it with "production").

❯ npm i rollup-plugin-replace
+ [email protected]
added 1 package from 1 contributor and audited 7985 packages in 3.54s
found 0 vulnerabilities
import resolve from "rollup-plugin-node-resolve";
import commonjs from "rollup-plugin-commonjs";
import replace from "rollup-plugin-replace";

export default {
  plugins: [
    resolve(),
    commonjs({
      namedExports: {
        react: ["useEffect"]
      }
    }),
    replace({
      "process.env.NODE_ENV": JSON.stringify("development")
    })
  ]
};
❯ rollup --config --format=esm main.js > bundle.js
main.js → stdout...
created stdout in 3.6s

Finally, the browser renders “text” as expected, and in the console we see:

My useEffect code is running.

But there’s no “Monkey!” in the logs! If we modify main.js to use React.useEffect we do see it though:

function App() {
  React.useEffect(() => {
    console.log("My useEffect code is running.");
  }, []);
  return "test";
}
❯ rollup --config --format=esm main.js > bundle.js
main.js → stdout...
created stdout in 3.7s
Monkey!
My useEffect code is running.

Let’s verify that it works differently with webpack. (Don’t forget to change back to a bare useEffect in main.js!)

❯ npm i webpack webpack-cli
+ [email protected]
+ [email protected]
added 278 packages from 170 contributors and audited 7989 packages in 6.987s
found 0 vulnerabilities
❯ webpack --mode development --devtool false --output bundle.js main.js
Hash: ba48f3ef1e7ae15f15ed
Version: webpack 4.31.0
Time: 605ms
Built at: 05/19/2019 2:24:08 PM
    Asset     Size  Chunks             Chunk Names
bundle.js  882 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./main.js] 298 bytes {main} [built]
[./monkey-patch.js] 213 bytes {main} [built]
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {main} [built]
    + 11 hidden modules

And the browser console does give both logs:

Monkey!
My useEffect code is running.

(It’s interesting to see how much webpack is doing out of the box for us, compared to Rollup’s more minimal and explicit approach.)

So … is Rollup wrong? Not really. Normally, you can’t change what a named export points to from another module.

// test.js
export let something = 1;
<!DOCTYPE html>
<meta charset="utf-8" />
<title>export re-assing test</title>
<script type="module">
  import { something } from "./test.js";
  something = 2;
</script>
TypeError: "something" is read-only

This doesn’t help either:

<!DOCTYPE html>
<meta charset="utf-8" />
<title>export re-assing test</title>
<script type="module">
  import * as all from "./test.js";
  all.something = 2;
</script>
TypeError: "something" is read-only

So it shouldn’t really be possible to monkey-patch a bare import of useEffect, if you’re following the spec closely. (Unless useEffect depends on a mutable object internally that you can modify – but that’s another story). It just happens to work because of implementation details of webpack and React.

The monkey-patching that stop-runaway-react-effects does should work any bundler if you use import React from "react"; React.useEffect. Which is kinda nice anyway since you don’t have to adjust your imports every time you use a new hook. On the other hand, most people use webpack anyway, so you should be fine either way. I would be surprised if webpack suddenly changed their output code in a breaking way.

If you’re interested, there’s an interesting discussion about shipping actual import/export in React to npm:

facebook/react#11503

Naming CSS colors

TL;DR If you can update the shade of a color really easily, you’ve won. Name your colors $red, $blue and $grey-whatever.

The problem

Let’s say you’re implementing some design that has a lot of this blue in it. In particular, this blue: #42aad5. Every time you need that blue, you simply write color: #42aad5;, background-color: #42aad5;, box-shadow: 0 0 5px #42aad5; or what have you. So far so good.

Then one day, the designer says to you: “You know that blue we use all over the place? Can you change it to #4bcafe, so that it ‘pops’ a little more?”

Now you have plenty of changes to make. Easy enough, though: Just do a find-and-replace, searching for #42aad5 and replacing with #4bcafe.

But shortly after your change, the designer comes to you again. “The new blue does not seem to show up on the dashboard, could you take a look at what’s going on?”

Turns out the blue had been written uppercase in the dashboard CSS for some reason: #42AAD5. Better remember to do a case insensitve find-and-replace next time.

But it doesn’t end there. If color-picking has been involved during the development, ever-so-slightly-off variations of the blue might exist. Perhaps your colleague color-picked out #42aad6 instead of #42aad5. Nobody’s eyes can tell the difference, but it’s enough to fool your find-and-replace.

Or consider this shade of blue: #44ccff. Somebody might have chosen to write it in shorter notation: #4cf.

A solution

Use a CSS preprocessor. I like Sass. You can also use Less, Stylus, PostCSS with some plugin, or custom properties in modern browsers, but I’ll go with Sass in this post.

In lots of projects I’ve worked on, there’s a _variables.scss file containing global variables for, well, global stuff. z-index values, breakpoints, some font stuff–and, most importantly, all colors.

With variables for colors, the shade update would have been super easy:

-$blue: #42aad5;
+$blue: #4bcafe;

Teach your team to look for colors in _variables.scss instead of hard-coding them and you should be fine.

The new problem

It is commonly said that one of the hardest things about programming is naming things. Which begs the question: How should you name your colors?

As a programmer, you might naturally raise your eyebrows when seeing that variable named $blue. Isn’t that like doing const TEN = 10 instead of const VELOCITY = 10?

You might be tempted to come up with some clever naming such as $primary-color and $secondary-color. After those two, though, I’ve basically run out of names (or at least they become harder and harder to come up with).

Besides some feeling of “purity,” what benefit would names like $primary-color provide?

Let’s say the designer changes their mind and decides green would be nicer than blue. You could of course just change the $blue variable to the new green color instead:

-$blue: #42aad5;
+$blue: #1feb8a;

But then you’d leave your colleagues and your future self confused.

Aha! If you’d named the variable $primary-color instead surely everything would have been fine?

The truth is, in my experience, that changing the shade of a color is much more common than changing to a whole other color. I’ve worked on a project where we switched from green and red, to blue and orange. I can tell you that the change was not as easy as just swapping all red for orange, and all green for blue. Some things simply didn’t look nice then. Changing colors is basically a light-weight restyling of the entire site, so you need to go through every page anyway to see that it all looks good with the new colors. Many times, more little adjustments than just color changes are needed. $primary-color and $secondary-color would not have helped.

On the other hand, I’ve also been trough changing a pinkish color to a more purpleish. That was as easy as doing a find-and-replace searching for $pink and replacing with $purple. And we’re not back at square one doing a find-and-replace again, because this time there’s no casing differences and whatnot to worry about.

Abstract color names buy you nothing other than making it harder to read and write your CSS (no matter what tools you use or how fancy your editor colors your variable names). Name your colors as simple as possible and save you some trouble. $blue, $orange and $red are just fine.

Simple names also make it easier to communicate with your designer. When I’m talking to designers, we you usually end up saying things like “the orange for links”, “the green for buttons”, or just “the red we use.” Not “the primary color is used for links“ and “the secondary color for buttons”. We’re just simple humans after all.

So I simply choose the same names as the designer uses when talking about the design. This is especially good for colors where people disagree on the naming, such as for some shades of cyan and blue, orange and red, or beige and brown.

No more excessive color picking or pasting hex codes in chat messages! By speaking the same “language” as the designer, you give the power to simply say “use the usual project blue for that button, please,” even to a new developer.

Yet a new problem

What about the cases where the designer does not have names for some colors?

It’s easy with “colorful” colors, such as the ones I’ve been talking about (red, green, blue, orange). There’s usually just a handful of those. Having too many makes for a messy design.

The annoying one is grey. In most projects I’ve worked on, there’s always a whole slew of greys. A good designer, in my opinion, might have a large amount of different greys, but at least is consistent with using only those. If there appears to be new greys in every part of a design, it might not be worth even trying to put them in variables.

How do you name those greys? What usually happened to me was that I named the first one I came across $grey. Next up, $grey-dark. Then I might have found a lighter one: $grey-light. Then, $grey-darker. $grey-even-darker. $grey-kinda-dark. You can see where this is going.

This “sorted names scheme” makes it hard to insert a new ones, for example between grey-light and grey-lighter. It’s also really hard to remember if it was grey-light or grey-lighter that was usually used for borders. And sometimes it’s hard to say if a grey is lighter or darker than another, and as such hard to come up with a name.

My solution is to give all the greys unique names that tell them apart, but doesn’t really say anything about the color. I’m a Star Wars fan, so I usually use Star Wars characters for the naming:

$grey-luke: #eeeef0;
$grey-lando: #969da6;
$grey-vader: #4d4d55;

Use your imagination! Choose something that you like–and your colleagues know about. If you’re the only Star Wars fan around, choosing another topic might be wiser.

If you want to take it a step further, you can let the name of a grey color give a hint about how dark it is. $grey-luke is probably a light grey, while $grey-vader is probably darker. Star Wars is good in this aspect, since the characters are usually obviously good (light) or evil (dark).

I think it’s fine to have $green-light and $green-dark. It’s when there’s three or more shades that things become hard. I’ve worked on a project that had almost as many shades of green as grey, and the Star Wars naming worked out there too.

It felt strange at first seeing border: 1px solid $grey-luke; in my CSS, but after a little while you almost grow a relationship with these “characters.” And I find it easier to tell $grey-luke and $grey-leia apart than $grey-light and $grey-lighter.

How far should you go?

Should every color be defined in _variables.scss? Only as long as it’s practical.

I usually use the regular old white and black CSS keywords for absolute whites and blacks. I’ve never had an issue with that so far.

Sometimes there are special parts of designs with one-off colors. I usually put them inline in the relevant .scss file and leave a comment that they are one-off. If the same one-off color is used several times within that .scss file, remember that you can make a local variable just for that file.

Conclusion

Colors are important. Make them easy to use. Easy to change. Easy to talk about with other people. Don’t make naming hard for yourself. Be practical. And allow a little fun in your CSS :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.