{
    "componentChunkName": "component---src-pages-post-slug-tsx",
    "path": "/2017/03/03/optimizing-image-sizes-in-hexo/",
    "result": {"data":{"post":{"slug":"2017/03/03/optimizing-image-sizes-in-hexo","title":"Optimizing image sizes in Hexo","date":"3 March 2017","parent":{"body":"var _excluded = [\"components\"];\nfunction _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n/* @jsxRuntime classic */\n/* @jsx mdx */\n\nvar _frontmatter = {\n  \"title\": \"Optimizing image sizes in Hexo\",\n  \"date\": \"2017-03-03T14:24:26.000Z\",\n  \"tags\": [\"node\", \"hexo\", \"image-processing\"],\n  \"lede\": \"An overview of my image-optimizing plugin for the static site generator.\",\n  \"project\": \"hexo-image-sizes\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n    props = _objectWithoutProperties(_ref, _excluded);\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"I use Hexo to generate my site. It\\u2019s a static site generator, and it follows the typical paradigm of keeping posts (in Markdown format) in a \", mdx(\"code\", {\n    parentName: \"p\",\n    \"className\": \"language-text\"\n  }, \"source\"), \" folder, along with images and other resources. The idea is to keep all the \\u201Coriginals\\u201D in the source directory, and then use the site generator to render them to their final display format, into an output directory.\"), mdx(\"p\", null, \"The problem is that source images are large. I want to keep them in their original full resolutions, because I might want to target bigger displays or allow people to download them, and in general because I want the source directory to be a complete archive of the site\\u2019s content.\"), mdx(\"p\", null, \"Instead of serving these huge images to the user, it would be better to make them as small as possible without degrading the experience of looking at them. How big they need to be depends on what they\\u2019re for. If they\\u2019re thumbnails in a gallery, for example, they should be really small files! A full-width banner image should obviously be larger. But none of these likely needs to be the original size, which is often on the order of 4 MB.\"), mdx(\"p\", null, \"Dynamic CMSs like Wordpress already solve this problem, by serving images derived from the ones you upload, resized based on your theme\\u2019s settings. For a static site, putting out images with different viewing \\u201Cprofiles\\u201D should be just as easy, but I didn\\u2019t see a way to do that with Hexo, so I wrote a plugin.\"), mdx(\"h2\", null, \"hexo-image-sizes\"), mdx(\"p\", null, \"The \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/ottobonn/hexo-image-sizes\"\n  }, mdx(\"code\", {\n    parentName: \"a\",\n    \"className\": \"language-text\"\n  }, \"hexo-image-sizes\"), \" plugin\"), \"\\nlets you define image profiles, which specify the optimal size for the image, and then it takes care of generating the images for you. Each source image is then available for use in the original format and in the format of each of the profiles.\"), mdx(\"p\", null, \"You can read about how to use it \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/ottobonn/hexo-image-sizes\"\n  }, \"on Github\"), \". I\\u2019m not going to cover that here; instead, I\\u2019d like to discuss some of the design decisions involved in making it.\"), mdx(\"h2\", null, \"How it works\"), mdx(\"h3\", null, \"The Hexo API\"), mdx(\"p\", null, mdx(\"code\", {\n    parentName: \"p\",\n    \"className\": \"language-text\"\n  }, \"hexo-image-sizes\"), \" is a Hexo plugin, which means it uses the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://hexo.io/api/\"\n  }, \"Hexo API\"), \" to interact with your site\\u2019s files. Hexo has a pretty flexible API, though the documentation is a little too terse for comfort sometimes.\"), mdx(\"p\", null, \"The plugin API revolves around these core concepts:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Plugins are \", mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"npm\"), \" modules whose names start with \", mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"\\\"hexo-\\\"\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Hexo loads plugins automatically at start and provides them with the \", mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"hexo\"), \" global instance object, which includes your site information\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Text files and (and anything else that registers) are \\u201Crendered\\u201D through renderer functions which receive the file data and return its rendered form\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Other files, like images, get copied directly to the output directory\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Plugins can register \", mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"processor\"), \" functions to listen for filenames matching a certain pattern and do things with the filenames\")), mdx(\"h3\", null, \"Renderer?\"), mdx(\"p\", null, \"My first attempt was to make a renderer for the images, to intercept them in the pipeline and resize them. There are two problems here.\"), mdx(\"p\", null, \"The first problem is that renderers are designed to transform the input file into some other, final format, and they can\\u2019t control the name of the output, or how many outputs there should be. So if we want to create multiple different sizes of one source image, a renderer isn\\u2019t the way to go.\"), mdx(\"p\", null, \"The other problem is that we don\\u2019t really want to read the image data into Javascript-land. The image files can be huge, and Hexo loads asynchronously and seemingly without any throttling all the files for which renderers have registered, so the memory penalty of loading all images in order to render them is not acceptable.\"), mdx(\"h3\", null, \"Processor!\"), mdx(\"p\", null, \"Instead, we want to keep the image handling as low-level as possible. The awesome \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/lovell/sharp\"\n  }, mdx(\"code\", {\n    parentName: \"a\",\n    \"className\": \"language-text\"\n  }, \"sharp\")), \" module uses a native-compiled \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"http://www.vips.ecs.soton.ac.uk/index.php?title=VIPS\"\n  }, mdx(\"code\", {\n    parentName: \"a\",\n    \"className\": \"language-text\"\n  }, \"libvips\")), \" under the hood to handle images, so the memory overhead should be much lower if we can just pass \", mdx(\"code\", {\n    parentName: \"p\",\n    \"className\": \"language-text\"\n  }, \"sharp\"), \" the name of an input file and the path to which to write the resized image.\"), mdx(\"p\", null, \"The \\u201Cprocessor\\u201D abstraction in Hexo gives us visibility into the stream of files Hexo is processing for the site. Each processor is simply a function, and it is invoked with a Hexo \", mdx(\"code\", {\n    parentName: \"p\",\n    \"className\": \"language-text\"\n  }, \"File\"), \" object as its only argument. The important keys in this object are the \\u201Ctype\\u201D and \\u201Csource\\u201D.\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"The file \", mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"type\"), \" indicates what Hexo is doing with it. It\\u2019s one of these strings: \", mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"\\\"create\\\"\"), \", \", mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"\\\"update\\\"\"), \", \", mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"\\\"skip\\\"\"), \", \", mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"\\\"delete\\\"\"), \" (as seen in \", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://github.com/hexojs/hexo/blob/5234c4a85dc6cd418e9a1c169e43de169cf98e95/lib/box/file.js#L33-L36\"\n  }, \"the source code\"), \"; this is an example of where the docs are lacking). For now, I only look at files that we\\u2019re creating or updating, but in the future I should probably handle deleting files as well.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"The \", mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"source\"), \" contains the file\\u2019s full path on disk.\")), mdx(\"p\", null, \"Now we simply need to tell \", mdx(\"code\", {\n    parentName: \"p\",\n    \"className\": \"language-text\"\n  }, \"sharp\"), \" this file\\u2019s \", mdx(\"code\", {\n    parentName: \"p\",\n    \"className\": \"language-text\"\n  }, \"source\"), \" and where to\\nwrite the output.\"), mdx(\"h3\", null, \"Wither output?\"), mdx(\"p\", null, \"So here\\u2019s the ugly hack of using a processor for generating these images: we receive information about the input file, but we have to \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"guess\"), \" the name of the output file. From what I can tell, Hexo simply uses the \", mdx(\"code\", {\n    parentName: \"p\",\n    \"className\": \"language-text\"\n  }, \"public_dir\"), \" configured in \", mdx(\"code\", {\n    parentName: \"p\",\n    \"className\": \"language-text\"\n  }, \"_config.yml\"), \" as the base path, and then tacks on the path of the input file relative to the \", mdx(\"code\", {\n    parentName: \"p\",\n    \"className\": \"language-text\"\n  }, \"source\"), \" directory. However, that could change, and my plugin wouldn\\u2019t work anymore.\"), mdx(\"p\", null, \"I name the output files based on the original filename and the name of the current profile. So if we have a profile called \\u201Cthumbnail\\u201D, then an image called \\u201Ccat.jpg\\u201D will end up in the output directory at full size as \\u201Ccat.jpg\\u201D and as a thumbnail at \\u201Cthumbnail-cat.jpg\\u201D.\"), mdx(\"p\", null, \"There\\u2019s one more complication, which is that the plugin will run when Hexo starts up. Unfortunately, that means we\\u2019re running ahead of the renderers, and it could be that none of the output directory structure exists yet. After deciding on the full output path, we have to make sure that path exists. I\\u2019m using \", mdx(\"code\", {\n    parentName: \"p\",\n    \"className\": \"language-text\"\n  }, \"mkdirp\"), \" to create a directory with all of its intermediate ancestors if it doesn\\u2019t already exist.\"), mdx(\"h3\", null, \"Using the images\"), mdx(\"p\", null, \"So we\\u2019ve resized the images, and now the output directory contains a copy of the original and potentially several resized copies with various names. We could just use one of these new filenames in our Markdown posts directly, but that situation is brittle, because changing the names of profiles could cause images not to show up at all.\"), mdx(\"p\", null, \"While we\\u2019re messing with the images, it\\u2019s a good time to note that Markdown doesn\\u2019t have a mechanism for captioning images, besides repeating some HTML snippet for each one. This too is brittle, and mixes elements of the site\\u2019s theme into the source posts, which is not what we want.\"), mdx(\"p\", null, \"Unfortunately (or maybe fortunately, depending on your bent), Markdown doesn\\u2019t have an official means of extension, so I can\\u2019t build support for captions and image profiles directly. Hexo allows Nunjucks tags in posts through its \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://hexo.io/api/tag.html\"\n  }, \"Tag API\"), \", which is the next best option. The upside is that we get a lot more options for controlling the output now, but the tradeoff is that we\\u2019re no longer writing standard Markdown, and our posts depend on this plugin now. I considered this trade for a few days, and decided this was the best course, but it was a tough call.\"), mdx(\"h3\", null, \"The \", mdx(\"code\", {\n    parentName: \"h3\",\n    \"className\": \"language-text\"\n  }, \"imsize\"), \" tag\"), mdx(\"p\", null, \"I wanted a tag that is as self-documenting as possible, so if this plugin dies or the posts need to migrate to a different platform, it will be as easy as possible to support these image tags.\"), mdx(\"p\", null, \"There are two choices for passing data to Nunjucks tags: \\u201Cargs\\u201D and \\u201Ccontent.\\u201D A tag looks like this:\"), mdx(\"div\", {\n    \"className\": \"gatsby-highlight\",\n    \"data-language\": \"text\"\n  }, mdx(\"pre\", {\n    parentName: \"div\",\n    \"style\": {\n      \"counterReset\": \"linenumber NaN\"\n    },\n    \"className\": \"language-text line-numbers\"\n  }, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"{% blockquote David Levithan, Wide Awake %}\\nDo not just seek happiness for yourself. Seek happiness for all.\\nThrough kindness. Through mercy.\\n{% endblockquote %}\"), mdx(\"span\", {\n    parentName: \"pre\",\n    \"aria-hidden\": \"true\",\n    \"className\": \"line-numbers-rows\",\n    \"style\": {\n      \"whiteSpace\": \"normal\",\n      \"width\": \"auto\",\n      \"left\": \"0\"\n    }\n  }, mdx(\"span\", {\n    parentName: \"span\"\n  }), mdx(\"span\", {\n    parentName: \"span\"\n  }), mdx(\"span\", {\n    parentName: \"span\"\n  }), mdx(\"span\", {\n    parentName: \"span\"\n  })))), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"args\"), \" is an array formed from the string passed in the tag line. Here, it is \", \"[\\u201CDavid\\u201D, \\u201CLevithan,\\u201D, \\u201CWide\\u201D, \\u201CAwake\\u201D]\", \". Hexo parses the arg line \", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://github.com/hexojs/hexo/blob/5234c4a85dc6cd418e9a1c169e43de169cf98e95/lib/extend/tag.js#L86-L113\"\n  }, \"here\"), \".\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"code\", {\n    parentName: \"li\",\n    \"className\": \"language-text\"\n  }, \"content\"), \" is the string between the opening and closing tags.\")), mdx(\"p\", null, \"Given that I want self-documenting tags, I\\u2019m not too worried about brevity.\\nCopy-and-paste is pretty easy, and authors can set up editor shortcuts to insert these tags if needed.\"), mdx(\"p\", null, \"Passing arguments through the arg line becomes unreadable for more than even a few arguments. The spec for that blockquote tag is:\"), mdx(\"div\", {\n    \"className\": \"gatsby-highlight\",\n    \"data-language\": \"text\"\n  }, mdx(\"pre\", {\n    parentName: \"div\",\n    \"style\": {\n      \"counterReset\": \"linenumber NaN\"\n    },\n    \"className\": \"language-text line-numbers\"\n  }, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"{% blockquote [author[, source]] [link] [source_link_title] %}\"), mdx(\"span\", {\n    parentName: \"pre\",\n    \"aria-hidden\": \"true\",\n    \"className\": \"line-numbers-rows\",\n    \"style\": {\n      \"whiteSpace\": \"normal\",\n      \"width\": \"auto\",\n      \"left\": \"0\"\n    }\n  }, mdx(\"span\", {\n    parentName: \"span\"\n  })))), mdx(\"p\", null, \"These are all positional arguments, and any of them can be elided! Handing that is not easy, and it doesn\\u2019t scale nor is it self-explanatory later.\"), mdx(\"p\", null, \"Instead, I decided to embed a \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"http://yaml.org/start.html\"\n  }, \"YAML\"), \" document in the tag\\u2019s content. YAML is very easy to read, and I don\\u2019t have to write a parser for it. By using a YAML document in the tag content, I can add more arguments in the future without breaking existing documents.\"), mdx(\"p\", null, \"Tags look like this:\"), mdx(\"div\", {\n    \"className\": \"gatsby-highlight\",\n    \"data-language\": \"text\"\n  }, mdx(\"pre\", {\n    parentName: \"div\",\n    \"style\": {\n      \"counterReset\": \"linenumber NaN\"\n    },\n    \"className\": \"language-text line-numbers\"\n  }, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"![Dell Precision 5510 repair](../uploads/2017/01/05/5510-repair.jpg)\"), mdx(\"span\", {\n    parentName: \"pre\",\n    \"aria-hidden\": \"true\",\n    \"className\": \"line-numbers-rows\",\n    \"style\": {\n      \"whiteSpace\": \"normal\",\n      \"width\": \"auto\",\n      \"left\": \"0\"\n    }\n  }, mdx(\"span\", {\n    parentName: \"span\"\n  })))), mdx(\"p\", null, \"We can specify the \", mdx(\"code\", {\n    parentName: \"p\",\n    \"className\": \"language-text\"\n  }, \"src\"), \" and \", mdx(\"code\", {\n    parentName: \"p\",\n    \"className\": \"language-text\"\n  }, \"profile\"), \", and alt text. In the future, we could add a caption, and potentially even an output format (.webp, for example). I\\u2019d like to add those possibilities soon.\"), mdx(\"p\", null, \"Since this is normal YAML, other people can write parsers for this tag for their blogging platforms if needed. It\\u2019s a little wordy, but I think it\\u2019s very clear.\"), mdx(\"p\", null, \"There\\u2019s more to come on this project, hopefully soon! I\\u2019m already using this plugin to generate the images on this site. Head over to the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/ottobonn/hexo-image-sizes\"\n  }, \"Github repo\"), \" to try it out!\"));\n}\n;\nMDXContent.isMDXComponent = true;","timeToRead":6}}},"pageContext":{"id":"740f5480-d5c4-5f1f-bad2-29a9399e54bf","slug":"2017/03/03/optimizing-image-sizes-in-hexo","__params":{"slug":"2017"}}},
    "staticQueryHashes": []}