About webpack have already been written on Habré, told on moscowjs and there are several articles on other resources that describe the overall capabilities of the webpack, its advantages and disadvantages.
Therefore, in this article we will talk about the webpack itself only briefly and in more detail about the development of a plug-in for it.
')
1. Webpack in brief
For build systems like popular
grunt or
gulp , a large number of auxiliary libraries are written, but webpack currently cannot boast of such abundance and it would be great to be able to use the necessary library. If you decide to implement a webpack in your project, but there is no ready solution, then the only way out is to write it yourself.
And so, what is a webpack, what is its feature and how does it work?
Webpack is a module bundler, as the developers themselves are positioning (
http://webpack.imtqy.com ).
Out of the box, it supports AMD, CommonJS and ES6 modules, there is the possibility of live reload'a, implemented in addition webpack-dev-server and working through socket.io. This means that you do not need every time after making changes to the source files - run the assembly commands again.
Also, webpack has a fairly simple config, not as transparent as gulp, but much more concise and neat.
Terminology:
2. Loaders & Plugins
Loader is a powerful tool used to upload, process and convert files. Those. if our project uses such technologies as, for example, jade, sass, coffee, es6, or our svg are too big and need to be pressed, all similar processing, transformation and minification tasks will be solved by just loaders. They have already been written quite a large number, you can find it
here .
Write your loader is simple. To do this, on the official website of the project there is a separate section -
How to write a loader and, in general, by the first lines of the code, you can immediately understand how simple it is:
Loader is a module.exports function, which accepts the contents of a file as input and returns the result of processing, and can also return a sourcemap if the necessary attribute is present in the configuration.
Returning the current context, it is possible to use chaining. Accordingly, each of them should perform only one, their own task, the developers of webpack call us to write a separate loader for each step. In general, this is a general modular ideology, when each module is encapsulated, due to which it is much easier to work with it and debug it.
A plugin is an object that has a apply method with just one parameter - compiler. Through it, you can communicate with the various stages of compilation webpack'a. To perform the necessary transformations, there are also various types of plug-in interfaces, the following code is given as an example on the official website:
compiler.plugin("compile", function(params) {
When you run a task to build a project, in the terminal you will see a message about the current build process, either an error caused by missing the required library or a problem with the syntax of the code.
When we started the build process using webpack, the code will work and in the terminal we will see: “Compiling ...”
Outwardly, this is a clear mechanic, but there are few examples of such work, and there are practically no examples in the documentation.
In part, this prompted us to write an article, because The
How to write a loader section has a lot of examples of loaders, but the
How to write a plugin section is hidden (and possibly removed altogether) from the official site for an indefinite period. By and large, we only have a list and a text description of the work of the so-called
interface types (
upd : now this section is hidden) and the opportunity to examine the code of already written plugins, see how it is used and works.
Of course, we have no doubt that the developers of the webpack will soon provide us with this section, but we will try to sort out this topic now to solve current problems, and when the section is opened, we will find something new for ourselves, and maybe even rethink working with plugins.
3. An example of solving a problem - gluing together svg sprites.
Webpack has been around for several years, but many still do not dare to use it as the main tool for working with a project, its capabilities are great and it can replace a number of tools at once for both assembling and working with modules.
And rewriting a project that is going to grunt'om or gulp'om along with all the tasks and plug-ins on the mechanics of the webpack is not 15 minutes. Especially considering that so far under the webpack there are not that many libraries that older and running assembly systems can boast of.
Suppose that we decided on this and began to update our project. And just in the process, you can stumble upon the problem that was described above - the webpack may not have a tool that duplicates or performs the same mechanics of the task that we previously had in the project and worked fine.
It happened with
grunt-svgstore - a library that collects all svg from the directory we need into one sprite, putting a prefix to connect the necessary svg in the right places of the page via
use xlink: href = “# logo” .
This is a very convenient mechanism for working with svg through the sprite and really would not want to lose this functionality in the transition process.
There is a similar solution under gulp, but under webpack it was not possible to find it.
This is a problem and a way out of it - write your own plug-in under the webpack, save the general mechanics on gluing svg sprites and, perhaps, add some new features and capabilities to it.
4. The process of writing code
In order to start writing, it is important to understand that our goal is not to steal someone else’s code and pass it off as your own, but you don’t want to reinvent the wheel either, especially considering the limited time that is always tight.
Therefore, here we will go this way: let's take the grunt-svgstore library code as a basis, change it a bit to start working as a plug-in for webpack and add some new features, while at the very top of the plug-in code we’ll point out the whole truth:
Thus, we will not deceive anyone and will not offend the developer who wrote the svgstore under grunt. In general, we undertook this plugin not for one-time use in a specific project, but to put it on npm with a successful outcome, thus making our contribution to the open source community.
To convert the task code into a plugin, we actually remove the entire grunt from the code, saving only the ready-made mechanism for directly assembling and gluing the svg to the sprite. But then we come across a number of problems that need to be fixed:
5. Unexpected problems
1) The sprite is too big, and we need to add a minification.
The first thing that was found is that under the webpack there is a ready-made svg loader (https://github.com/rpominov/svgo-loader) under the hood of which there is a svgo, through which you can drive all our svg'shki.
We connect in the plugin module svgo:
var SVGO = require('svgo');
And add the minification function of our svg:
SvgStore.prototype.svgMin = function(file, loop) { var svgo = new SVGO(); var source = file; var i; function svgoCallback(result) { source = result.data; }
It happens that minification can spoil some svg, we also strive to ensure that the process of adding a new svg is minimal in terms of labor costs. We can achieve this by adding an option to the
min: true / false plugin, by which we understand whether we will minify certain svg's, or we will assemble all svg exactly as it is.
The size of the sprite is important, and the smaller it will be, the naturally better. But to completely abandon all minification is not very correct, so we decide to add an internal directory for svg that will be minified, i.e. for example, the path to our directory with svg:
/ app / assets / svg
And those svg, which we want to minimize, we put in a nested directory:
/ app / assets / svg / min
Thus, we will only have svg lying in the internal min directory minified, and we don’t need to refuse all minification if it breaks several icons.
Still there is a nuance because of which the function we have takes 2 parameters, one of which is the contents of svg, and the second is loop.
The fact is that one of the advantages of svg is that we can control the styles of our image through css, change the color, width of lines, etc.
But after the 1st run, svgo does not delete inline styles, because of which we cannot redefine them via css, i.e. if we need to change the color for example on a hover, this will not work in some cases. But after 2 runs, these attributes are erased and the override starts to work.
Naturally, not everyone needs this, so the default of the loop option is one (1 run), but we can specify any number of runs through svgo.
2) It is necessary with the help of the plugin to collect a few sprites. This was implemented using prefixes rather primitive and not very nice (but working) code, the option was called filter and works as follows:
1. 'except-name' - if the value begins with the words except-, then all the svg will be collected in the sprite, except for those that in this case begin with the name
2. 'name' - svg will be compiled into the sprite, starting with the specified string, in this case with the name
output: [ { filter: 'Logo-', sprite: 'svg/[hash].logo_sprite.svg' }, { filter: 'except-Logo-', sprite: 'svg/[hash].sprite.svg' } ]
3. 'all' - if we do not need to divide anything and we want to get at the output only 1 sprite, which will contain all the svg.
output: [ { filter: 'all', sprite: 'svg/[hash].sprite.svg' } ]
In addition to the filter, we also specify the directory where all our assets are going, and the [hash] option has also been added. In the hashing method itself, we again did not reinvent the wheel, but took the most standard method of hashing that the crypto module uses:
Name.replace('[hash]', crypto.createHash('md5').update(source).digest('hex'))
Initially, it was not planned to add a hash to the collected sprites, because it seems like webpack should do such things and our task is only to slip the assembled sprite during the compilation process prior to adding hashes, and the collector will do the rest. But, alas, it wasn’t possible to make it work that way, so we add the hash inside the plugin ourselves.
3) Another big problem is the addition of collected sprites to the manifest.
The manifest is json in which the correspondence with the name of the assets to the collected names with hashes and paths is indicated. This is done to connect assets, since we do not know what hash each assembly will have. With the help of the manifest, we get a convenient table of links. For this we use the
manifest for webpack.
With the help of this manifesto the back of our project understands what needs to be connected after the new assembly.
And here there is another problem that we encountered: the compiler gives up 2 objects, in one of which an array of assets, in the second - modules.
And after adding an asset inside the plugin
compiler.plugin('emit', function(compilation, callback) { compilation.assets[key.sprite] = { source: function() { return new Buffer(source); }, size: function() { return Buffer.byteLength(source, 'utf8'); } }; callback(); });
it appears in the compiler.assets array, but this array does not give us a match between the file name - and its path with the hash.
That is why the developer who wrote the manifest-webpack plugin uses compiler.modules, not comiler.assets, inside his plugin, although the latter would be much more logical.
Those. after we added a new asset, following the example of the code that
Tobias (one of the webpack developers)
suggested to us, this asset appears in the assets array, but this array does not contain the original name or path, so we cannot use it.
We tried to insert a name field via
pull request so that we could use the assets array to build a manifest, but it was not accepted. And in general, this idea was rejected.
This gave rise to the problem of adding our newly created sprites to the manifest, and at this stage it is necessary to manually manipulate the plugin at the final stage:
compiler.plugin('done', function(compilation, callback) {
And add sprites via
fs.readFile / fs.writeFile to the manifest , which is not good.
6. Examples and prospects of use
You can poke and try the resulting plugin in your webpack project by following the
link .
In the future, it is planned to make the plug-in the most universal, to refactor the code, remove all unnecessary and cover its work with tests - generally leave as little of what is necessary and make it as stable as possible.
7. References
Demo with the result of the plug-in - all icons on the site
motor.ru
Plugin repository (waiting for your issue and pull requests) -
github.com/lgordey/webpack-svgstore-plugin
Plugin in NPM -
www.npmjs.com/package/webpack-svgstore-plugin
Webpack documentation -
webpack.imtqy.com/docs
Webpack repository -
github.com/webpack/webpack