📜 ⬆️ ⬇️

Another post about building a front-end project

Js app starter

I spent decently time structuring and automating the assembly of the front. The task is interesting and worth mentioning.

What the collector can do:


Introductory


To make it easier to follow the thought, I immediately throw a link to the repository with the project template: github.com/alexfedoseev/js-app-starter
')
How to start it
Make sure that npm is installed.
npm -v 


Install the necessary global modules (if not already installed):
 npm install -g gulp browserify babel jade stylus http-server 


Fork the repository.
 git clone https://github.com/alexfedoseev/js-app-starter.git 


Install the project dependencies (execute in the repository root):
 npm install 


Build the project in the development environment and start the local server:
 npm start 


Open a browser and go to lvh.me:3500

We will use Gulp as a collector.
What does the assembly process include and what technologies are used:

In general, I love Slim and Sass, but Ruby to Ruby, a JS to JS: for the frontend project we will use only pieces from npm. If desired, any tool can be replaced.

Project structure


 | dist/ | lib/ |-- gulp/ |-- helpers/ |-- tasks/ |-- config.js | node_modules/ | public/ |-- css/ |-- files/ |-- fonts/ |-- img/ |-- js/ |-- json/ |-- favicon.ico |-- index.html | src/ |-- css/ |-- files/ |-- fonts/ |-- html/ |-- img/ |-- js/ |-- json/ |-- sprite/ |-- favicon.ico | .gitignore | .npmignore | gulpfile.js | npm-shrinkwrap.json | package.json 
Github

.gitignore & .npmignore
Inside these files is a list of what git and npm will be ignored when commits / publish.

node_modules /
All the modules that we install via npm fall into this directory.

npm-shrinkwrap.json
I do not keep node_modules / contents in the repository. Instead, I am locking all dependencies through this file It is generated automatically by the command: `npm shrinkwrap` .

package.json
This is a file with global project settings. To him back again.

gulpfile.js
Usually, all the tasks for building the project are stored here, but in our case it simply determines the value of the environment variable and sends us further to the folder with gulp-tasks.

lib / gulp /
Here we store all the settings and tasks of the collector.

| - config.js
We carry out the settings for all tasks in a separate file in order to minimize editing of the tasks themselves.

| - helpers /
Auxiliary methods of the collector.

| - tasks /
And yourself gulp-taski.

src /
Sources of the project.

public /
The result of the assembly. Absolutely all the contents of this folder is generated by the collector and it is completely cleaned before each new build, so we never store anything here.

dist /
Sometimes I write opensource modules. After assembling, this folder contains the regular and minified version of the written js-library. In this case, the public / directory is used as a repository for the demo. If you make a regular site or landing page, then it will not be needed.

Project Setup


package.json

This is the file where the global project settings are stored.
A detailed description of his insides can be found here: browsenpm.org/package.json
Below I will focus only on some important parts.

 { //   "name": "js-app-starter", //   //     /  js+css        "version": "0.0.1", //    js-,      , //     `require('your-lib')` "main": "./dist/app.js", //  browserify //    ,      ES6  ES5 "browserify": { "transform": [ "babelify" ] }, //   ( ) "scripts": { "start": "NODE_ENV=development http-server -a lvh.me -p 3500 & gulp", "build": "NODE_ENV=production gulp build" }, //  jshint (  ) "lintOptions": { "esnext": true ... }, // Frontend  "dependencies": { "jquery": "^2.1.3" ... }, // Development  "devDependencies": { "gulp": "^3.8.11" ... } } 
Github

Console commands

In package.json, we can prescribe aliases for console commands that we will often perform during the development process.

 "scripts": { "start": "NODE_ENV=development http-server -a lvh.me -p 3500 & gulp", "build": "NODE_ENV=production gulp build" } 


Development build
Before starting work with the project we need:

 # ,   npm start #      NODE_ENV=development http-server -a lvh.me -p 3500 & gulp 

We disassemble in parts
 #    NODE_ENV=development #      lvh.me   3500 http-server -a lvh.me -p 3500 #  gulp  gulp 



Production build
When we are ready to release a project, we make a production assembly.

 #  Ctrl+C,      ,    # ,   npm run build #      NODE_ENV=production gulp build 

We disassemble in parts
 #    NODE_ENV=production #  gulp- `build` gulp build 



Gulp


Go to Gulp. The structure of the task is taken from the collector by Dan Tello .

Before diving, a small comment on the order of execution of the usual gulp-task:

 var gulp = require('gulp'); gulp.task('task_1', ['pre_task_1', 'pre_task_2'], function() { console.log('task_1 is done'); }); //    `task_1`,      `task_1 is done` //    `gulp task_1` //     `task_1`    `['pre_task_1', 'pre_task_2']` //  ,  'pre_task_1' & 'pre_task_2' -  , //           , //  `task_1`    ,   2 pre- -    


Now we will understand what and in what order we will collect.

Development build
`npm start` runs the` gulp` command. What happens next:


Production build
Everything is easier with her. `npm run build` runs the` gulp build` command , which clears the target folders, scans the js code, builds the sprites and then builds the project (without sourcemaps). Code with comments above.

Gulp-task configuration file

All basic configurations of tasks are moved to a separate file lib / gulp / config.js :

 /* file: lib/gulp/config.js */ var pkg = require('../../package.json'), //  package.json bundler = require('./helpers/bundler'); //      /*   */ var _src = './src/', //    _dist = './dist/', //       _public = './public/'; //         var _js = 'js/', //   javascript  _css = 'css/', //   css _img = 'img/', //    _html = 'html/'; //   html /* *  js / css  * * : app.js, app.css -  * admin.js, admin.css -  * * : your-lib.js -    * your-lib.jquery.js -    jquery- * */ var bundles = [ { name : 'app', //   global : 'app', //   ,   ,      compress : true, // ? saveToDist : true //    `/dist`? (true -   , false -   ) } ]; module.exports = { /*    */ }; 
Github

HTML build

For templating we use Jade . It allows you to insert partials, use inline-javascript, variables, mixins and many more cool stuff.

Gulp
Config

 /* file: lib/gulp/config.js */ html: { src: _src + _html, //   jade- dest: _public, //    params: { //   jade pretty: devBuild, //    html? locals: { // ,      pkgVersion: pkg.version //      `pkgVersion` } } } 
Github

Task

 /* file: lib/gulp/tasks/html.js */ var gulp = require('gulp'), jade = require('gulp-jade'), jadeInherit = require('gulp-jade-inheritance'), gulpif = require('gulp-if'), changed = require('gulp-changed'), filter = require('gulp-filter'), notifier = require('../helpers/notifier'), config = require('../config').html; gulp.task('html', function(cb) { //   jade-   src/html gulp.src(config.src + '*.jade') //  dev-,  watcher     .pipe(gulpif(devBuild, changed(config.dest))) //    .pipe(jadeInherit({basedir: config.src})) //  - ( `_` ) .pipe(filter(function(file) { return !/\/_/.test(file.path) || !/^_/.test(file.relative); })) //  jade  html .pipe(jade(config.params)) //  html- .pipe(gulp.dest(config.dest)) //     .on('end', function() { notifier('html'); //  (  + ) cb(); // gulp-callback,     }); }); 
Github
Sources
Folder structure src / html

 | src |-- html |-- index.jade #   |-- components/ #   |-- _header.jade |-- helpers/ # ,  |-- _params.jade |-- _mixins.jade |-- meta/ #  head,    . |-- _head.jade 
Github

All partials are prefixed with `_` (underscore), so that we can filter and ignore them during assembly.

helpers / _variables.jade
We save necessary parameters in variables. For example, if our phone is in several places on a page, then it’s better to save it into a variable and use it in templates.

 /* file: src/html/helpers/_variables.jade */ - var release = pkgVersion //   gulp- - var phone = '8 800 CALL-ME-NOW' //  
Github

helpers / _mixins.jade
Frequently used blocks can be wrapped in mixin.

 /* file: src/html/helpers/_mixins.jade */ mixin phoneLink(phoneString) - var cleanPhone = phoneString.replace(/\(|\)|\s|\-/g, '') a(href="tel:#{cleanPhone}")= phoneString //    // +phoneLink(phone) 
Github

index.jade
The skeleton of the main page.

 /* file: src/html/index.jade */ include helpers/_variables //   include helpers/_mixins //   doctype html html head include meta/_head body include components/_header include components/_some_component include components/_footer 
Github

meta / _head.jade
Content head.

 /* file: src/html/meta/_head.jade */ meta(charset="utf-8") ... //   ,    js/css    link(rel="stylesheet" href="css/app.min.css?v=#{release}") script(src="js/app.min.js?v=#{release}") ... 
Github


Javascript build

We use Browserify as a modular system. With it we can use the style of connection of CommonJS modules directly in the browser. In addition, we can now use the ES6 syntax: Babel converts it to ES5 before Browserify collects js. And before building, we run jsHint to check the quality of the code.

Browserify has one drawback: if you are writing a library with external dependencies (for example, a jQuery plugin), then it will not be able to make the correct UMD wrapper. In this case, I replace Browserify with concatenation and write the wrapper with my hands.

About bandla
On the project it may be necessary to form several sets of js / css.

For example, you write front + admin panel. Or a library in 2 versions: no dependencies and jQuery-plugin format. These assemblies need to be shared. To do this, we create an array in the collector settings:

 /* file: lib/gulp/config.js */ /*   */ var bundles = [ { name : 'myLib', //   global : 'myLib', //   ,      compress : true, // ? (   ) saveToDist : true //    `/dist`? } ]; /*   /   */ var bundles = [ { name : 'app', //   global : false, //     compress : true, // ? saveToDist : false //    `/dist`? }, name : 'admin', global : false, compress : true, saveToDist : false } ]; 
Github

js / css collectors will search the folder with the js / css source for the corresponding end-point file (`app.js` or` app.styl`). Through this end-point file, we manage all dependencies of the bundle. I will show their structure below.

Before transferring the bundles to the assembler, we first pass the array through the `bundler` helper , which forms the object with the settings.
Gulp
Config

 /* file: lib/gulp/config.js */ scripts: { bundles: bundler(bundles, _js, _src, _dist, _public), //   banner: '/** ' + pkg.name + ' v' + pkg.version + ' **/\n', //     min.js extensions: ['.jsx'], //    lint: { //   jshint options: pkg.lintOptions, dir: _src + _js } } 
Github

Task

 /* file: lib/gulp/tasks/scripts.js */ var gulp = require('gulp'), browserify = require('browserify'), watchify = require('watchify'), uglify = require('gulp-uglify'), sourcemaps = require('gulp-sourcemaps'), derequire = require('gulp-derequire'), source = require('vinyl-source-stream'), buffer = require('vinyl-buffer'), rename = require('gulp-rename'), header = require('gulp-header'), gulpif = require('gulp-if'), notifier = require('../helpers/notifier'), config = require('../config').scripts; gulp.task('scripts', function(cb) { //  -  var queue = config.bundles.length; //     ,    , //      bundle-   //      var buildThis = function(bundle) { //  bundle browserify var pack = browserify({ //   sourcemaps cache: {}, packageCache: {}, fullPaths: devBuild, //   end-point (app.js) entries: bundle.src, //   ,     // browserify    UMD- //        bundle.global standalone: bundle.global, //   extensions: config.extensions, //  sourcemaps? debug: devBuild }); //  var build = function() { return ( // browserify- pack.bundle() //  browserify-  vinyl .pipe(source(bundle.destFile)) //   ,    `require`   .pipe(derequire()) //  dev-,      `public/` ( -  )) .pipe(gulpif(devBuild, gulp.dest(bundle.destPublicDir))) //     `dist` -  .pipe(gulpif(bundle.saveToDist, gulp.dest(bundle.destDistDir))) //     sourcemaps   .pipe(gulpif(bundle.compress, buffer())) //  dev-    —  sourcemaps .pipe(gulpif(bundle.compress && devBuild, sourcemaps.init({loadMaps: true}))) //  .pipe(gulpif(bundle.compress, uglify())) //      `.min` .pipe(gulpif(bundle.compress, rename({suffix: '.min'}))) //    production -        .pipe(gulpif(!devBuild, header(config.banner))) //  sourcemaps .pipe(gulpif(bundle.compress && devBuild, sourcemaps.write('./'))) //     `/dist` .pipe(gulpif(bundle.saveToDist, gulp.dest(bundle.destDistDir))) //   `public` .pipe(gulp.dest(bundle.destPublicDir)) //    callback handleQueue ( ) .on('end', handleQueue) ); }; //   watchers if (global.isWatching) { //  browserify-  watchify pack = watchify(pack); //      -   pack.on('update', build); } //     var handleQueue = function() { // ,    notifier(bundle.destFile); //    if (queue) { //   1 queue--; //    ,  ,    if (queue === 0) cb(); } }; return build(); }; //      config.bundles.forEach(buildThis); }); 
Github
Sources
Src / js folder structure

 | src/ |-- js/ |-- components/ #   |-- helpers/ # js- |-- app.js # end-point  
Github

app.js
Through this file we drive all dependencies and order of execution of js-components. The file name must match the name of the bundle.

 /* file: src/js/app.js */ /* Vendor */ import $ from 'jquery'; /* Components */ import myComponent from './components/my-component'; /* App */ $(document).ready(() => { myComponent(); }); 
Github
What if there is no dependency in npm
In such cases, use browserify-shim : a plugin that allows you to turn ordinary libraries into CommonJS-compatible modules. So, we have a jQuery plugin `maskedinput` , which is not in npm.

Add a transformation to `package.json` and set the options for the dependency:

 /* file: package.json */ "browserify": { "transform": [ "babelify", "browserify-shim" //   ] }, //  `browserify-shim`     //    github: https://github.com/thlorenz/browserify-shim "browser": { "maskedinput": "./path/to/jquery.maskedinput.js" }, "browserify-shim": { "maskedinput": { "exports": "maskedinput", "depends": [ "jquery:jQuery" ] } } 


After that we can connect the module:
 require('maskedinput'); 


CSS build

We use Stylus as a preprocessor. Plus, we pass on css with autoprefixer , so as not to prescribe vendor prefixes with our hands.

Gulp
Config

 /* file: lib/gulp/config.js */ css: { bundles: bundler(bundles, _css, _src, _dist, _public), //   src: _src + _css, //      watcher params: {}, //     stylus -   autoprefixer: { //  autoprefixer browsers: ['> 1%', 'last 2 versions'], //     cascade: false //   ,    }, compress: {} //     -   } 
Github

Task

 /* file: lib/gulp/tasks/css.js */ var gulp = require('gulp'), process = require('gulp-stylus'), prefix = require('gulp-autoprefixer'), compress = require('gulp-minify-css'), gulpif = require('gulp-if'), rename = require('gulp-rename'), notifier = require('../helpers/notifier'), config = require('../config').css; /*  css-   js- */ gulp.task('css', function(cb) { var queue = config.bundles.length; var buildThis = function(bundle) { var build = function() { return ( gulp.src(bundle.src) .pipe(process(config.params)) .pipe(prefix(config.autoprefixer)) .pipe(gulpif(bundle.compress, compress(config.compress))) .pipe(gulpif(bundle.compress, rename({suffix: '.min'}))) .pipe(gulp.dest(bundle.destPublicDir)) .on('end', handleQueue) ); }; var handleQueue = function() { notifier(bundle.destFile); if (queue) { queue--; if (queue === 0) cb(); } }; return build(); }; config.bundles.forEach(buildThis); }); 
Github
Sources
Src / css folder structure

 | src/ |-- css/ |-- components/ #   |-- header.styl |-- footer.styl |-- globals/ |-- fonts.styl #   |-- global.styl #    |-- normalize.styl #  /  |-- variables.styl #  |-- z-index.styl # z-  |-- helpers/ |-- classes.styl #   |-- mixins.styl #   |-- sprite/ |-- sprite.json # json,  gulp.spritesmith |-- sprite.styl #   json css- |-- vendor/ #  css   |-- app.styl # end-point  
Github

app.styl
Through this file we rule the connection order of css-components. The file name must match the name of the bundle.

 /* file: src/css/app.styl */ @import "helpers/mixins" @import "helpers/classes" @import "globals/variables" @import "globals/normalize" @import "globals/z-index" @import "globals/fonts" @import "globals/global" @import "sprite/sprite" @import "vendor/*" @import "components/*" 
Github


All other tasks - pictures, sprites, cleaning, etc. - do not require additional comments (in fact, I was just tired of scribbling). Sources are in the repository: github.com/alexfedoseev/js-app-starter

If there are shoals or additions - I will be glad to feedback through the comments here or issues / pull requests on Github. Good luck!

Source: https://habr.com/ru/post/251807/


All Articles