Foreword
The quality of the application depends not only on what tasks and at what speed it solves, but also on such seemingly secondary factors as “code beauty”.
By the beauty of the code, I (I suppose many others) understand:
- Readability
- Easy to change and add
- The ability for others to figure out how it works
Everyone at the dawn of his development path wrote code that was able to solve a certain (often even difficult) task, but when trying to change or adapt something to a similar task, problems arose.
And the presentability of such a code was in doubt.
')
Let's deal with two tools that, in spite of their simplicity, will increase the presentability of the source code of your application and restore order in your head.
Gulp
What is gulp?
This is a project collector.
What is it for?
The question is more complicated.
Without losing the convenience of developing the output, you get the project in the form in which it should be:
- Optimized images
- Minimized styles and scripts
- Other
In fact, you get two copies of the application: a working one, which is understandable to you, in which it is convenient to make edits, and public (public), already assembled from your pieces and optimized.
Speaking about the project assembled from the pieces, I had in mind the implementation of a modular approach to programming, which, in my opinion, is extremely useful and is the starting point for the development of the developer. The reasons for this are obvious: the ability to easily integrate any module into any project (without finishing the file), ease of testing and, most importantly, order in the head.
Gulp is essentially a js script (set of scripts) that runs on a node.js server.
This does not mean that you need to learn nodejs in order to use gulp, but you will need basic knowledge of its package manager (npm).
Task one: put the
node.js server locally.
I will not focus on this, too simple. After installation we can use npm.
Task two: install (in fact, download) gulp using the package manager.
For makovodov this is as follows: in the terminal we will write
npm install gulp -g
The -g flag means that we set it globally so that you can start it in the terminal with the command
gulp gulp task
Then we set up the environment for our project by going to the console in the right folder.
mkdir assets public assets/js assets/img assets/css touch gulpfile.js npm init npm i --save-dev gulp
In order:
- Create a folder structure
- Create a file of our collector settings
- We initialize npm to install useful modules in the assembly
- Install gulp with the key "--save-dev", that is, in the directory of our project
Similarly, with the last line we will install all the modules we need in the assembly.
npm i --save-dev gulp gulp-autoprefixer gulp-concat-css gulp-connect gulp-livereload gulp-minify-css gulp-sass gulp-rename gulp-uncss gulp-uglify gulp-imagemin imagemin-pngquant
Briefly about them- gulp-autoprefixer // Adds styles with prefixes to support browsers
- gulp-concat-css // merges styles into one file
- gulp-connect // Server
- gulp-livereload // Refreshes the page in the browser after code changes
- gulp-minify-css // Style minification
- gulp-sass // No comment (not yet needed)
- gulp-rename // renames files
- gulp-uncss // Removes extra styles
- gulp-uglify // Scripting minification
- gulp-imagemin // Image minification
- imagemin-pngquant // Image minification
Examples of use and connection are most clearly described
here .
Now all we need is to describe the settings file, how exactly our project should be assembled.
To do this, in our configuration file (gulpfile.js) we will create tasks (tasks)
gulp.task('js',function(){
For independent study I leave the full code of my
gulpfile.jsvar gulp = require ('gulp');
var concatCss = require ('gulp-concat-css');
var minifyCss = require ('gulp-minify-css');
var rename = require ("gulp-rename");
var autoprefixer = require ('gulp-autoprefixer');
var livereload = require ('gulp-livereload');
var connect = require ('gulp-connect');
var sass = require ('gulp-sass');
var uglify = require ('gulp-uglify');
var imagemin = require ('gulp-imagemin');
var pngquant = require ('imagemin-pngquant');
// Basic
gulp.task ('css', function () {
gulp.src ('./ assets / css / *. css')
.pipe (concatCss ("style.min.css"))
.pipe (minifyCss ({compatibility: 'ie8'}))
.pipe (autoprefixer ({
browsers: ['last 10 versions'],
cascade: false
}))
.pipe (gulp.dest ('./ public / css /'));
gulp.src ('./ assets / css / fight / *. css')
.pipe (concatCss ("fight.min.css"))
.pipe (minifyCss ({compatibility: 'ie8'}))
.pipe (autoprefixer ({
browsers: ['last 10 versions'],
cascade: false
}))
.pipe (gulp.dest ('./ public / css /'))
.pipe (connect.reload ());
});
gulp.task ('sass', function () {
gulp.src ('./ assets / sass / *. ccss')
.pipe (sass ("style.css"))
.pipe (minifyCss (''))
.pipe (rename ("style.sass.min.css"))
.pipe (autoprefixer ({
browsers: ['last 10 versions'],
cascade: false
}))
.pipe (gulp.dest ('./ public / css /'))
.pipe (connect.reload ());
});
gulp.task ('html', function () {
gulp.src ('./ assets / *. html')
.pipe (gulp.dest ('./ public /'))
.pipe (connect.reload ());
});
gulp.task ('fonts', function () {
gulp.src ('./ assets / font / ** / *')
.pipe (gulp.dest ('./ public / font /'))
.pipe (connect.reload ());
});
gulp.task ('js', function () {
gulp.src ('./ assets / js / *. js')
.pipe (uglify ())
.pipe (gulp.dest ('./ public / js /'))
.pipe (connect.reload ());
});
gulp.task ('jslibs', function () {
gulp.src ('./ assets / js / libs / *. js')
.pipe (uglify ())
.pipe (gulp.dest ('./ public / js / libs /'))
.pipe (connect.reload ());
});
gulp.task ('jsmods', function () {
gulp.src ('./ assets / js / modules / ** / *. js')
.pipe (uglify ())
.pipe (gulp.dest ('./ public / js / modules /'))
.pipe (connect.reload ());
});
gulp.task ('img', function () {
gulp.src ('./ assets / img / *')
.pipe (imagemin ({
progressive: true
svgoPlugins: [{removeViewBox: false}],
use: [pngquant ()]
}))
.pipe (gulp.dest ('./ public / img /'))
.pipe (connect.reload ());
});
// connect
gulp.task ('connect', function () {
connect.server ({
root: 'public',
livereload: true
});
});
// Watch
gulp.task ('watch', function () {
gulp.watch ("./ assets / css / ** / *. css", ["css"]);
gulp.watch ("./ assets / *. html", ["html"]);
gulp.watch ("./ assets / js / *. js", ["js"]);
gulp.watch ("./ assets / js / libs / *. js", ["jslibs"]);
gulp.watch ("./ assets / js / modules / ** / *. js", ["jsmods"]);
});
// Default
gulp.task ('default', ["html", "css", "sass", "js", "jslibs", "jsmods", "connect", "watch"]);
Focus only on a couple of things.
Any of the created tasks (tasks) is launched from the console by the template.
gulp task
If we write just “gulp”, that will start the default task, that is, default.
Task Watch - built-in taskbar to track changes in files. Its setup is so obvious (see the code above) that it’s sure everyone can handle it. This task allows you not to call the project build process every time after any code changes. As soon as you save the file, gulp will see it and rebuild the project, and in this case it will also refresh the page in the browser, and all you have to do is look at the desired monitor to see the result.
To build a project with the above settings, just type in the console (being in the project folder)
gulp img gulp
Work with pictures made in a separate task for convenience (mine).
After that, do not close the console, you have a watch'er and server running.
In fact, you can write a small js function in each separate file, and the collector will collect all this into one file.
Here we came close to the issue of modularity. The above situation is very similar to the modular approach (from afar) - each function (module) is a separate file. You will not get confused. But what to do when one module depends on another, on several others, and those on others.
Yes, there is already more difficult, it is necessary to think about the correct order of connecting modules. And here we are come to the aid of requirejs, which implements the AMD (Asynchronous module definition) approach (there is also common.js, but about it in the next article).
Require.js
Require.js will be mastered on a live example. In
one of the previous articles, we made a card toy. As a result, we got just a
bunch of code , which we will now rake.
First of all,
download reuirejs and connect it to our index.html, it will be the only script that we will connect in this way.
<script data-main="js/config" src="js/libs/require.js"></script>
In the data-main parameter, we pass the path to the entry point of our application (we do not specify the extension).
Config.js in the simplest case is a list of aliases (although you can do without it)
Config.js requirejs.config({ paths: { "rClck" : "modules/disable_rclck", "app" : "app", "socket" : "modules/webSockets", "Actions" : "modules/Actions", "User" : "modules/User", "userList" : "modules/userlist", "Matreshka" : "libs/matreshka.min", "Modal" : "modules/Modal", "fight" : "modules/fight", "jquery" : "libs/jquery", "myCards" : "modules/myCards", "opCards" : "modules/opCards", "mana" : "modules/mana" } }); require(['app'],function(app){ app.menu(); });
The requirejs syntax is simple - we pass an array of dependencies (both paths and aliases are valid, everything is specified without the js extension), we describe a function that works after dependencies are satisfied (do not forget to pass modules on which we depend as arguments).
require(['app'],function(app){ app.menu(); });
We connected the app module, after we call the menu () method of this module.
The description of the modules is as follows: we pass an array of dependencies, we write a callback function, which returns our module.
define(['socket'],function(socket){
The socket.readyState method checks if there is a connection to the server. If yes, then we call the Actions.exec method, if not, the modal window (Modal.box).
Actions, socket and Modal are separate independent modules on a par with the app module. They have their own dependencies, their own logic, their own methods.
Everything that we previously had in
one file , we broke into modules. Just because logically they are different pieces of code that are responsible for different tasks, whether it is connecting to the server via WebSockets, manipulating maps or “other tasks”.
For other tasks, I refer to what connects all the modules. We used to have it all in the Actions object
Actions var Actions = { send: function(method, args){ console.log('send: ' + method); args = args || ''; socketInfo.method = method; socketInfo.args = args; socket.send(JSON.stringify(socketInfo)); }, resume: function(){ User = JSON.parse(localStorage['PlayingCardsUser']); this.send('reConnect',User.login); }, setMotion: function(login){ console.log('setMotion: ' + login); User.currentMotion = login; this.motionMsg(); this.getCard(); this.setTimer(); } ...
Data from the server comes in JSON format as
{'function': 'fooName', 'args': [/ ]}
And earlier we called the necessary method as follows:
socket.onmessage = function (e){ if (typeof e.data === "string"){ var request = JSON.parse(e.data); Actions[request.function](request.args); }; }
But as our application grows and becomes more complex, the Actions object will grow to a level where it will be difficult to keep it in memory and navigate it. That is why we divide it into small modules (the simplest units that perform an elementary function. I saw this ideology in the gulp, and I liked it).
First we need a wrapper for calling the Actions method.
define(["socket"],function(socket){
In the exec method of the Actions module, as the first argument, we take the name of our action (action), the remaining arguments are passed as arguments to this module.
In the simplest case, the action module will look like this:
define(['myCards'],function(myCards){
This module is a wrapper for calling the add card method.
myCards.hand.add(card);
This is necessary in order to isolate the modules from each other as much as possible. So that at any time you can rewrite it painlessly, for example, using another framework.
Theoretically, it would be possible to immediately receive information from the server about how to run the myCards.hand.add () method, but we are working with what we have. We are trying to figure out how to clean up a bunch of code that already exists, sort it out.
So, having initially one js file, where everything was described from and to, we divided it into modules responsible for the implementation of the cards (on the table and in the hand of each player), websockets and others. And also let's write a new module and implement it to better understand everything. This will be the mana module:
mana.js define(['Matreshka'],function(Matreshka){
A module is a list (array) of mana crystals. What should he be able to?
- Add crystal on each turn: add ()
- Wasting full (active) mana crystals: spend ()
- Replenish (make active) mana crystals every turn: setAllActive ()
The point is that other modules do not need to know how these methods are implemented. It is necessary to rewrite the module without any serious consequences for the same opportunity. The main thing is to keep the logic.
Let's introduce our new module into the existing toy, let the cost of the cards now be taken into account.
Start by replenishing mana crystals every turn. Each turn begins with a call to the action (the module called by the Actions module through our exec method) setMotion, which sets which player walks now.
setMotion.js define(['Actions', 'User', 'myCards','mana'],function(Actions, User, myCards, mana){ var action = { run: function(login){ User.currentMotion = login; Actions.exec('motionMsg');
As you can see, the implementation of the first step was not difficult. We simply added a call to this method at the right time (at the beginning of our turn). And if we rewrite our module, everything will work as it worked. The main thing is not to forget to implement the same logic.
How to spend mana? Obviously, this happens at the stage of putting cards out of hand on the table. We make a change to this method:
It was this.on('click::sandbox',function(){
It became this.on('click::sandbox',function(){
Conclusion
Applying easy-to-learn things, the study of which does not take you more than one day, we made a significant leap forward on the way to a quality application.
Of course, the code is still far from ideal, but much more readable, more presentable and allows you to work on it further. It's easier to force yourself to rewrite a small piece than a huge script.
We will continue to improve this toy in the process of learning new technologies. To fill my hand, I recommend to take some of your old project (not the easiest, so that was where the cones fill) and redo it with us.
Repetition is the mother of learning.
Links