Frontend gulp.js-based (v4) build framework. A prepared, configurable environment available as a global package (possible to install locally as well). Automatically produces clean and optimized output code. A perfect solution for any frontend work.
Development is based on fully customizable bundles (currently available only simple default bundle), which modify the core configuration and provide directory structure. Supported JavaScript modes: script (concatenated and minified) and module (ES2015 imports via webpack).
The framework is intended for use in any size projects, including those that need be developed quickly, e.g in digital agencies. Produces optimized code, that contains usually single, minified JavaScript and CSS files.
What distinguishes this tool is basically:
- core, including tasks, separated from your project code (as a global Node.js package):
- you can update it independently and use new features
- create your own preconfigured starter-bundles, e.g. for AngularJS, React, Wordpress or just for specific project types (e.g. clients) with common configuration
- you don't have to run
npm install
and wait to initialize a project - just start coding - after some time you can go back to an old or other developer's project and use the same API
- fully customizable structure, ability to setup exact directory paths (e.g. source image files can be located in
~/Somewhere/On/The/Moon/
, dist HTML files in../public_html
and JavaScript files indist/js
) - by default, in script mode (non-webpack), a single output JavaScript file is built (
app.js
), also for the dev environment (with source mapping):- adding any new file using Bower, manually placing any package into
vendor/js
dir (if you can't, don't have time or just don't want to use Bower) or creating your new source script, does not require any markup changes to include new file - you can, however, generate separate compositions, for example: a script consisting of jQuery (installed with Bower) and a
register.js
file (and mark the latter one as ignored in other scripts) - the output generation is optimized: vendor files are watched separately and cached, so if you change your own code these are just prepended
- adding any new file using Bower, manually placing any package into
- automatically creates separate sprite images for
src/sprites
subdirectories (and you can use them responsively) clean
task does not remove the whole dist directory, but handles them separately; that's why you can mix your framework assets with files from other sources (e.g. backend)- provides keyboard shortcuts (Ctrl+...) while watching: rebuild, build production version, lint
Thanks to the above parameters, it is very easy to integrate with a backend application, including classic, non-RESTful/SPAs (Single Page Applications).
The architecture, in few words, is as follows: when you invoke the main frs [task]
command, the script runs gulp in the framework directory (so it uses the core gulpfile.js
), but gets the assets from (and builds to) the directories defined in your configuration files. So you can consider it as a kind of gulp pipeline.
The result: you just develop fast. Modify/create new stylesheets or images and see your page automatically refreshing with changes. Put pictures into sprites dir and get all things done. Install or paste new JavaScript files and see the results instantly, with source maps.
The framework provides the following functionality via gulp plugins:
- Separate source and distribution directories (configurable path), watching for new/changed files
- Images: imagemin for optimization, spritesmith for sprites
- JS: source maps (read about), ES2015 modules importing with webpack or concatenation into one file in script mode, compression, ESLint to check your code quality, Babel to provide ES2015 (AKA ES6) support, vendor dirs cache (concat only on change), custom file compositions
- Styles: SASS preprocessor with node-sass, source maps, Autoprefixer to handle vendor CSS prefixes, sass-glob to use globs in SASS imports, optimization: group-css-media-queries (media queries optimization by grouping - disabled by default as unsafe) and cssnano (with only safe optimisations by default). The default bundle comes additionally with Breakpoint library to handle media queries easily and SASS-core with responsive mixins and functions
- Views: optimized with htmlmin
- Server: Browsersync, providing automatic refresh on every change
- Easy to integrate with MV* frameworks and backend apps (see the bundles)
You need the following tools to start using the framework:
- Node.js
- for Windows, use the installer
- for Linux, the easiest way is to install via package manager
- gulp.js - install globally
Then, install the framework globally:
npm install frontend-starter -g
If you are experiencing any problems during installation, you probably have to update your Node.js (recommended nvm, then use the latest Node.js version) and npm. In some cases system restart may be needed before installation. If you use Visual Studio, close it while npm installs the modules.
Installation registers an frs
command to run the tasks.
Assuming you have Node.js installed, get the bundle (e.g. the default) and run:
npm install frontend-starter --save-dev
Then just use the script run syntax, e.g.:
npm run frs start -- -r
Note the --
separator for options (read more).
Use on of the available bundles (bootstrap configuration and asset structure) or create your own:
- default bundle
- AngularJS bundle (soon)
Use the following tasks from the command line:
frs start
For your first run, or when you want to rebuild a clean dist version. This will run both build
and watch
tasks.
frs
or
frs watch
Gulp watches for any changes in your src directory and runs the appropiate task.
frs build
Cleans and builds the dist version.
frs lint
Runs a linter (currently only ESLint).
frs clean
Cleans the dist directory.
frs build -p
Add the -p
parameter to any task, to get the optimized version. Or set NODE_ENV
variable to production
(e.g. $ NODE_ENV=production frs build
).
To restart the builder without opening a new Browsersync window in the browser, add a -r
parameter for the tasks: default (watch
) and start
, e.g.
frs -r
frs start -r
styles
, sprites
, fonts
, js
, images
, views
, custom-dirs
, browser-sync
While watching for changes (tasks: frs
/frs watch
or frs start
), you can use the following shortcuts:
- Ctrl+P: to build the production version (init
build -p
task) - Ctrl+D: to build the development version (init
build
task) - Ctrl+L: to run lint (init
lint
task) - Ctrl+C: to exit
See your bundle docs.
All files placed in the sprites directory will be merged into one sprite image (by default - sprites.png
). To create more than one sprite bundle, place them in separate directories (directory name will be the output sprite filename by default). Example usage:
/src/sprites/home/
icon-1.png
icon-2.png
/src/sprites/contact/
icon-1.png
icon-2.png
/src/sprites/ (root dir)
icon-1.png
icon-2.png
This will generate sprite files in /dist/img/
directory: home.png
, contact.png
and sprites.png
. To use a sprite in your stylesheets, include the generated SASS file(s) and then use the mixins:
// To get /src/sprites/home/icon-1.png
@include sprite($home-icon-1);
// To get /src/sprites/contact/icon-2.png
@include sprite($contact-icon-2);
// To get /src/sprites/icon-1.png
@include sprite($icon-1);
As you can see, by default each Spritesmith generated sprite variable has a prefix equal to the directory name (except for the root dir, that has none).
You can customize any of the directories options, by adding an item explicitly in the config file. For example, this will disable a variable prefix for sprites from /home
directory (so you can use it like it was in the root):
config.sprites.items.push({
name: 'home',
varPrepend: ''
}
See more in the default bundle example configuration.
For third-party scripts, you can use Bower or place any file in the /vendor/js
directory. For your own code, just create any file in src/js
. By default, all of them will be merged into single app.js
file (in the above order).
You can generate separate JavaScript compositions, dependent on selected Bower, vendor and/or own (app) script files. Let's say, that you want to create register.js
output file, that uses jQuery, register.js
and utilities.js
from the sources. We assume, that we don't want these files to be included in our main app.js
file:
config.js.comps.register = {
filename: 'register', // Set to null to not produce any output file (for sub-comps); if not set, defaults to comp id
bower: [], // Set only name of the package
vendor: [], // Just an example, you don't have to define when not used
app: ['utilities.js', 'register.js'], // Path relative to the appropriate directory
// Set prioritized paths
priority: {
vendor: [],
app: ['utilities.js'] // This file will be included before register.js
},
// Set other comp ids to include
dependencies: ['jQuery'],
// Exclude (ignore) all loaded here scripts in other comps, e.g.
// excludeIn: ['comp1', 'comp2'] // Excluded in selected comps
// excludeIn: true // Excluded in all other comps
// excludeIn: false // No exclusion
excludeIn: true, // Here: we exclude it in any other comps
watch: true // Not needed, watch blocked only if false
}
// We didn't include jQuery directly in the "register" comp, because in that case it would also be ignored in other comps
config.js.comps.jQuery: {
filename: null, // We don't want to create any output - this is just an auxiliary comp
bower: ['jquery'],
watch: false
}
The basic configuration is:
config.js.comps.main = {
filename: 'app', // Entry: a filename or glob, e.g. ['app', 'app2'] (.js extension appended automatically if dot not found)
webpack: ['**/*.js'] // Files to watch; disables bower, vendor and app props
}
Remember to update the linter option:
config.lint.options.parserOptions.sourceType = 'module';
The framework uses ESLint to check the code standards and quality. By default, the JavaScript Standard Style with commas is assumed, i.e. JavaScript Semi-Standard Style.
Frontend starter comes with Semi-Standard, Standard and classic ESLint configurations (like eslint:recommended
) installed. To change the base config to one of them, you have to create a .eslintrc
file in you app's root directory with the following example configuration:
{
extends: "eslint:recommended",
parserOptions: {
ecmaVersion: 6,
sourceType: "module"
}
}
Due to the ESLint architecture, you cannot change the "extends" value in an inline way (in the frs.config.js
) - the configuration file is the only solution.
In the case you are using Frontend starter as a global module and want to use any other base configuration than the listed preinstalled ones (extends: "some-custom-config"
), apart of the config module itself, you will have to additionally install gulp-eslint
locally (npm i gulp-eslint --save-dev
). This is needed because ESLint script looks for the configuration module from its location perspective - without the local installation, it would be the global framework's node_modules and would fail to load it, since it is installed locally. The framework will detect that you have a local gulp-eslint
and use it instead of the preinstalled (global) one.
Images are optimized (for production, gulp-imagemin) and copied into the dist directory.
You can setup custom directories to watch (and optionally copy). For example, if you integrate the framework with backend that has own view templating system, set the appropriate directory to be watched. In this case, you will also probably need to set Browsersync proxy (see the default bundle example configuration) to use the original server (like Apache) but have your page being refreshed on each time the view or any other files change.
Example - watch and copy contents of "php" dir from src to dist:
config.customDirs.items.push({
name: 'php views', // Optional, displayed in the console during watch
src: dirs.src.main + 'php/**/*.php',
dest: dirs.dist.main + 'php/' // Set to null to just watch the dir without copying (e.g. external backend views)
});
Browsersync provides a simple HTTP server with auto-refreshing on each change.
All configuration definitions are placed in core files: gulpfile.dirs.js and gulpfile.config.js. See the default bundle config files for common examples and the dir or config sources for full list of options. It's very simple.
To change the defaults, edit the frs.dirs.js
, frs.config.js
and frs.tasks.js
files located in your bundle root directory.
You can see the default definitons of each directory in the gulpfile.dirs.js file. The frs.dirs.js
is included in two stages:
- right after defining the src and dist directory (so you can change it and the value will populate to subdirectories like images, styles...)
- at the end (to change particular src/dist directories). See the default bundle dir config.
See the gulpfile.config.js file. config
object contains configuration parameters divided into key sections. Use the subsets to target specific environment mode: dev
and prod
.
- styles: sourcemap generation, gulp-autoprefixer, gulp-sass and gulp-cssnano options
- sprites: you can customize sprite files by adding subsequent elements to the
items
array - js: sourcemap generation, minification, merging vendor and app into one file (true by default), scripts loading priority
- images: imagemin options
- views: gulp-htmlmin options
- customDirs: custom, additional directories (to watch/copy)
- browserSync: Browsersync options
- clean: modify deletion options
See the default bundle custom config for examples.
Each gulp pipeline step has a kind of hook, that allows to inject own code and/or disable default stream transformation. Consider the following configuration code for styles
task:
var config = {
// (...)
styles: {
// (...)
inject: {
src: true, // Function must return: a stream (if cancels) or a glob array passed to the src
sourceMapsInit: true,
sassGlob: true,
sass: true,
autoprefixer: true,
optimizeMediaQueries: false, // group-css-media-queries, disabled by default as unsafe
optimize: true, // cssnano
sourceMapsWrite: true,
dest: true,
finish: true,
reload: true
},
dev: {
// (...)
inject: {
optimizeMediaQueries: false,
optimize: false
}
}
// (...)
To remove any of above steps, set the inject config value to false
:
config.styles.inject.optimize = false;
To add any transformation before the step, or replace it, assign it to a function:
var cleanCss = require('gulp-clean-css');
config.styles.inject.optimize = function(stream) {
// stream: current stream
stream = stream.pipe(cleanCss());
// return stream; // If you don't want to cancel the original step
// ...but we do want to cancel the default step (cssnano)
return this.cancel(stream);
}
This replaces gulp-cssnano with gulp-clean-css (you have to run npm install gulp-clean-css --save-dev
first).
The src
injects are handled a bit differenlty. The function obtains a glob array to be passed to the gulp.src
. If the default step is canceled, the return value is considered as an input stream (unless it is falsy - then the whole task is canceled). In other case, the returned value must be a glob (e.g. you can change the default one), that will be used by gulp.src
.
For the clean
task, the inject function receives current glob array with paths to be removed (assigned incrementally).
Available properties available from within the inject function are:
this.task
: task namethis.appData
: an object{ dirs, config, app, tasks, taskReg, gulp, browserSync }
this.taskParams
: an object with task paramters (common param for all tasks isisWatch
that indicates that it was called by the watcher; see the tasks source for other params, defined at the beginning of the run method)this.taskData
: an object created for some tasks with additional info:sprites
:{ itemInfo }
(current item config object)js
:{ comp, filename, filenameVendor, vendorDir }
(current internal comp object, not the comp config definition)customDirs
:{ dirInfo }
(current item config object)
this.isDev
: indicates whether dev or prod mode
All tasks to be registered in gulp are organized in the taskReg
object. Consider the following example definition that may be placed in the frs.tasks.js
file:
appData.taskReg['mytask'] = {
fn() {
var stream = appData.gulp.src(appData.dirs.src.images)
.pipe(appData.gulp.dest('/some-dir'));
// You can also access config, e.g. check if dev vs. prod mode
//console.log(appData.config.main.isDev);
// Currently all tasks must return a promise
return appData.app.taskUtils.streamToPromise(stream);
},
deps: ['clean', ['js', 'views']],
// blockQuitOnFinish: true // Set this option only for persistent tasks like watching for changes (by default any task function is wrapped to ensure process quitting)
}
This adds mytask
task, that copies contents of images dir to somewhere.
The deps
is an array of tasks invoked before. It follows the run-sequence convention: by default tasks are run synchronously (one by one), but placing them in an array will allow to run asynchronously (at once). In the above example, js
and views
will run in parallel after clean
is complete. This notation is converted internally to gulp 4's series/parallel.
To add a task as a dependency to an existing task, use the helper:
// appData.app.taskRegUtils.addDep(taskToAdd, targetTask, relatedTask, isBefore);
appData.app.taskRegUtils.addDep('mytask', 'build', 'images', true);
The above code adds mytask
to the build
pipeline, before images
(last parameter defines the placement, for false it would be placed after).
To replace the original (core) task, just override its appData.taskReg
definition.
To remove a core task, or remove a dependency, use the helpers:
// Removes views task (and from all tasks' depenedencies)
appData.app.taskRegUtils.removeTask('views');
// Removes images dependency from build task
appData.app.taskRegUtils.removeDep('images', 'build');
// Removes images dependency from any task
appData.app.taskRegUtils.removeDep('images', true);
See the core tasks registry definitions in gulpfile.tasks.js.
To use gulp.js directly, not through the frs
command, clone this repo into a desired directory, run npm install
and then directly gulp [task]
. The framework will look for configuration files in the parent directory (../
).