WebBlocks is package, configuration and dependency manager for web assets (SCSS, JS, images, and fonts).
This repository contains the code base for WebBlocks 2.0, which is currently under development and not intended for use at this time. For those interested in using WebBlocks today, please see WebBlocks 1 under the ucla/WebBlocks repository.
WebBlocks is open-source software licensed under the BSD 3-clause license. The full text of the license may be found in the LICENSE file.
WebBlocks is managed by Eric Bollens as part of a collaboration between the University of California, the Mobile Web Framework community and others.
WebBlocks is a product of its contributors, including those who have contributed code, submitted bugs or even simply participated in the dialogue.
WebBlocks is built on top of a number of outstanding open source platforms and packages including Ruby, RubyGems, Bundler, Node.js, npm, Saas, Compass, Thor, Rake, File System State Monitor, sass-css-importer, ruby-bower, fork and extend_method. A sincere thanks is extended to the authors of all these fine tools.
NOTE Assuming installation in one of the ways described as follows, this documentation uses blocks
as a shell command; in cases where it's installed locally by bundle rather than globally, it may be necessary to use bundle exec blocks
instead.
The following are required:
- Ruby, RubyGems and Budler
- Node.js and NPM
- Java 1.4+ (only for compression)
Download or clone repository from:
Install Ruby dependencies:
bundle
To install the latest dev snapshot from RubyGems:
gem install web_blocks --pre
Or add it to your Gemfile
(creating it if it doesn't exist):
gem 'web_blocks'
Alternatively, to get the bleeding edge, you may specify the Git repository for sources:
gem 'web_blocks', :git => 'https://github.com/WebBlocks/WebBlocks.git'
Once added to your Gemfile
, run Bundler to install it:
bundle
WebBlocks requires Bower to be installed.
The easiest way to do this:
npm install -g bower
Additionally, you must ensure that your rc file (.bashrc
, .zshrc
, etc.) properly sets $NODE_PATH
to the node_modules
directory where global installs are placed. If it does not, you may want to add something of the form:
export NODE_PATH=$(npm config get prefix)/lib/node_modules
An alternative is that, in the directory where you're going to compile WebBlocks (the same as where bower.json
will be placed), you may do a local install of bower:
npm install bower
Alternatively, this can be done by defining a package.json
:
{
"dependencies": {
"bower":">=1.3.8"
}
}
And then simply calling:
npm install
WebBlocks provides a blocks
executable that wraps several commands. These commands include:
inspect
, which analyzes a WebBlocks configuration;build
, which produces a WebBlocks build;watch
, which will reproduce a WebBlocks build every time a source file changes;partial
, which allows one to run only one step in the build process.
Each of these have one or more actions below them, except partial, which actually has sub-commands below it, each with their own actions.
To see a list of all commands:
$ blocks help
To get information about a particular command:
$ blocks [command [subcommand]] help [action]
For just a command:
$ blocks build help
For a command and action:
$ blocks inspect help dependency_list
For a command, sub-command and action:
$ blocks partial compile help scss
All commands (except help
) accept several global options:
--base-path
defines an explicit directory wherebower.json
resides (and wherebower_components
,.blocks
andbuild
will be created). If this is not specified, WebBlocks will attempt to use the current working directory, or if there's nobower.json
file in there, it will traverse into parent directories searching for one, exiting with a failure if neither the current working directory nor any parent contains abower.json
file.--blockfile-path
defines an explicit Blockfile location relative to the current working directory. If this is not specified, thenBlockfile.rb
is assumed to reside in the--base-path
.--include
defines one or more blocks to include that aren't included by the blockfile itself. Route segments should be space-delimited, and includes should be comma-delimited:--include efx driver tabs, efx driver toggle
. If spaces or special characters are used, then the values must be enclosed in quotes or escaped.--reload-bower
to discard the components inbower_components
and fetch a fresh copy. This option should be used whenever dependencies are modified inbower.json
.--reload-registry
to discard the bower registry cache and use a fresh copy. This option should be used ifnode_modules/bower/bin/bower
is ever explicitly called.
This command will link, compile and produce a build into the build
directory:
blocks build
This command will watch for pertinent changes and rebuild into the build
directory:
blocks watch
Tests may be performed through rake
:
rake test
NOTE WebBlocks currently only has partial test coverage. Work is ongoing to provide full coverage.
The build process is composed of several separate steps. These should not usually be called on their own, except for testing, debugging or advanced use cases. Their outputs may be found under .blocks/workspace
.
An SCSS file composed of @import
rules may be generated into the temporary workspace:
blocks partial link scss
A JS file of all JS sources concatenated together may be generated into the temporary workspace:
blocks partial link js
The link phase is all that's needed to compose the WebBlocks Javascript; however, for SCSS, only an import sheet is generated, and thus an actual CSS file may be generated into the temporary workspace:
blocks partial compile scss
This command is used to inspect WebBlocks configuration.
To get a list of components to be registered based on bower.json
:
blocks inspect bower_registry
To get a list of blocks and files as defined by Blockfile.rb
:
blocks inspect blocks
To include attributes set on blocks and files:
blocks inspect blocks --attributes
To filter within a route:
blocks inspect blocks --route="WebBlocks-visibility"
NOTE Attributes here are raw as defined by the Blocksfile.rb
file and the Blockfile.rb
files of registered blocks; some files with :required = false
attributes here may still be included during the link phase on account of dependency resolution. When looking at what will be required, the dependency inspection actions should be used instead.
To get a list of dependencies per the configuration of Blocksfile.rb
and the blocks themselves:
blocks inspect dependency_list
To get an list of files in the order that they will be compiled by WebBlocks:
blocks inspect dependency_order
Both commands support a type option for the file type.
For example, to see only scss
files:
blocks inspect dependency_order --type=scss
Blocks are the fundamental constituents of WebBlocks, defined as bower components. Consequently, bower.json
must define a name and version for the build you're going to create. The name is important in that it defines the block that WebBlocks will load by default when compiling (although others may be included as dependencies or explicitly - more on this later):
{
"name": "my-app",
"version": "1.0.0"
}
Further, in this example, suppose the goal is to use the WebBlocks-visibility
block. This block resides in the Git repository WebBlocks/block-visibility, and thus it may be included by adding it to the dependencies
section of bower.json
:
{
"name": "my-app",
"version": "1.0.0",
"dependencies": {
"WebBlocks-visibility": "WebBlocks/block-visibility"
}
}
While blocks should carefully define their dependencies to be as accepting as possible, two blocks will sometimes require completely different versions of the same dependency. This can be resolved by manually specifying a resolution in bower.json
:
{
"resolutions": {
"jquery": "~2.1.0"
}
}
While packages define blocks to include, generally they will not be included unless specified explicitly.
To include all of WebBlocks-visibility
, create Blockfile.rb
with the following:
include 'WebBlocks-visibility'
This resolves out for compilation as:
$ blocks inspect dependency_order
/Users/ebollens/Sites/test/bower_components/WebBlocks-visibility/src/_hide.scss
/Users/ebollens/Sites/test/bower_components/WebBlocks-visibility/src/accessible/_hide.scss
/Users/ebollens/Sites/test/bower_components/WebBlocks-breakpoints/_variables.scss
/Users/ebollens/Sites/test/bower_components/WebBlocks-visibility/src/breakpoint/_hide.scss
/Users/ebollens/Sites/test/bower_components/WebBlocks-visibility/src/breakpoint/_hide-above.scss
/Users/ebollens/Sites/test/bower_components/WebBlocks-visibility/src/media-query/_hide.scss
WebBlocks has linked all WebBlocks-visibility constituents, as well as its requisites - namely WebBlocks-breakpoint (which we did not specify but was rather specified in the WebBlocks-visibility Blockfile.rb and bower.json files).
You can build it as:
$ blocks build
Alternatively, you can perform this same operation without a Blockfile.rb
defined in your workspace through the use of a command-line argument:
$ blocks build --include WebBlocks-visibility
NOTE: Using the --include
flag when the base directory (where bower.json
resides) contains a Blockfile.rb
will lead to all inclusions occurring both that are triggered by Blocksfile.rb
and by --include
. To skip using a Blockfile, simply add --blockfile-path FALSE
.
WebBlocks allows one to include sub-blocks rather than just full blocks.
Based on the configuration above, let's inspect the available blocks:
$ blocks inspect blocks
framework (Framework)
WebBlocks-visibility (Block)
hide (Block)
_hide.scss (ScssFile)
accessible (Block)
hide (Block)
_hide.scss (ScssFile)
breakpoint (Block)
hide (Block)
_hide.scss (ScssFile)
hide-above (Block)
_hide-above.scss (ScssFile)
media-query (Block)
hide (Block)
_hide.scss (ScssFile)
WebBlocks-breakpoints (Block)
_variables.scss (ScssFile)
Anything designated as a block may be specified as an include
. Returning the Blockfile.rb
, one could tighten the block inclusion scope by replacing its current content with something like:
include 'WebBlocks-visibility', 'accessible'
include 'WebBlocks-visibility', 'breakpoint'
This has tightened up the inclusions accordingly:
$ blocks inspect dependency_order
/Users/ebollens/Sites/test/bower_components/WebBlocks-visibility/src/accessible/_hide.scss
/Users/ebollens/Sites/test/bower_components/WebBlocks-breakpoints/_variables.scss
/Users/ebollens/Sites/test/bower_components/WebBlocks-visibility/src/breakpoint/_hide.scss
/Users/ebollens/Sites/test/bower_components/WebBlocks-visibility/src/breakpoint/_hide-above.scss
Again, build it as:
blocks build
Alternatively, without multiple scoped includes may be specified as:
$ blocks build --include WebBlocks-visibility accessible, WebBlocks-visibility breakpoint
In this example, our bower.json
file reads:
{
"name": "my-app",
"version": "1.0.0",
"dependencies": {
"WebBlocks-visibility": "WebBlocks/block-visibility"
}
}
For the purpose of this example, suppose we have two files:
src/app.js
src/styles.scss
These can be added to the Blockfile.rb
as follows:
include 'WebBlocks-visibility', 'accessible'
include 'WebBlocks-visibility', 'breakpoint'
block 'my-app' do
set :path, 'src'
js_file 'app.js'
scss_file 'styles.scss'
end
The items under my-app
will automatically be included because it has the same name as specified under bower.json
. This behavior means that, if this were to be included in another project with another name, then it would not be automatically included but rather have to be included like WebBlocks-visibility in this example.
When developing an application, calling build
can be repetitive. The watch
command gets rid of this repetition:
blocks watch
Any time that a bower component, blockfile or source file changes, it will rebuild the necessary components.
NOTE: The watch
command will not work properly under inotify (Linux) for modifications to Blockfile.rb
.
The WebBlocks-breakpoints module provides a set of variables that it will define unless they're already defined. This enables WebBlocks-visibility's breakpoint-based classes. Suppose, however, that we wish to inject our own variables.
To start things off, create src/config/WebBlocks-breakpoint.scss
such as:
$breakpoint-xxsmall: 360px !default;
$breakpoint-xsmall: 480px !default;
$breakpoint-small: 640px !default;
$breakpoint-medium-small: 768px !default;
$breakpoint-medium: 900px !default;
$breakpoint-medium-large: 1024px !default;
$breakpoint-large: 1200px !default;
$breakpoint-xlarge: 1500px !default;
$breakpoint-xxlarge: 1800px !default;
NOTE You should always use !default
when setting variables, so as to play nice with others who may want to explicitly define these variables earlier in the stack than your block.
Next, let's define Blockfile.rb
as follows, including this sub-block but dropping the includes from earlier examples:
block 'my-app', :path => 'src' do
js_file 'app.js'
scss_file 'styles.scss'
block 'config', :path => 'config' do
block 'WebBlocks-visibility' do
scss_file 'WebBlocks-breakpoints.scss'
reverse_dependency framework.route 'WebBlocks-breakpoints'
end
end
end
If we inspect this without any includes, the custom variables file has not been tacked on:
$ blocks inspect dependency_order
/Users/ebollens/Sites/test/src/app.js
/Users/ebollens/Sites/test/src/styles.scss
However, if we include something that requires the including of WebBlocks-breakpoints
, which has a dependency on our config file, our config file will get included before it:
$ blocks inspect dependency_order --include WebBlocks-visibility breakpoint
/Users/ebollens/Sites/test/src/config/WebBlocks-breakpoints.scss
/Users/ebollens/Sites/test/bower_components/WebBlocks-breakpoints/_variables.scss
/Users/ebollens/Sites/test/bower_components/WebBlocks-visibility/src/breakpoint/_hide.scss
/Users/ebollens/Sites/test/bower_components/WebBlocks-visibility/src/breakpoint/_hide-above.scss
/Users/ebollens/Sites/test/src/app.js
/Users/ebollens/Sites/test/src/styles.scss
Suppose that now we decide to scrap our work with WebBlocks-visibility and instead add the Efx library to our project:
{
"name": "my-app",
"version": "1.0.0",
"dependencies": {
"efx": "ebollens/efx"
}
}
Additionally, we delete Blockfile.rb
.
The blocks builder caches packages and metadata, so the first thing we need to do is reload it with the --reload-bower
flag on any command. In this case, let's inspect the bower components with the reload:
$ blocks inspect bower_registry --reload-bower
efx
/Users/ebollens/Sites/test/bower_components/efx
jquery
/Users/ebollens/Sites/test/bower_components/jquery
Following this, when we call blocks build
, we end up with empty files. If we use blocks inspect dependency_list
, it returns an empty set. We need to explicitly choose what we wish to support:
$ blocks inspect blocks --attributes
framework (Framework)
:required = true
efx (Block)
:required = false
:path = "src"
engine (Block)
:required = true
engine.js (JsFile)
:required = true
driver (Block)
:dependencies = [["efx", "engine"]]
:required = false
:path = "driver"
accordion (Block)
:required = false
accordion.css (ScssFile)
:required = true
accordion.js (JsFile)
:required = true
tabs (Block)
:required = false
tabs.css (ScssFile)
:required = true
tabs.js (JsFile)
:required = true
toggle (Block)
:required = false
toggle.css (ScssFile)
:required = true
toggle.js (JsFile)
:required = true
If we wanted to include everything:
$ blocks build --include efx
If we wanted only to include the Efx engine:
$ blocks build --include efx engine
Let's settle with taking two of its drivers, tabs and toggle:
$ blocks build --include efx driver tabs, efx driver toggle
Instead of using the command-line, add this as the contents of Blockfile.rb
:
include 'efx', 'driver', 'tabs'
include 'efx', 'driver', 'toggle'
An inspect dependency_order
shows us that, in addition to the drivers, the engine will also be included, given that it's a dependency:
$ blocks inspect dependency_order
/Users/ebollens/Sites/test/bower_components/efx/src/engine.js
/Users/ebollens/Sites/test/bower_components/efx/src/driver/tabs.css
/Users/ebollens/Sites/test/bower_components/efx/src/driver/tabs.js
/Users/ebollens/Sites/test/bower_components/efx/src/driver/toggle.css
/Users/ebollens/Sites/test/bower_components/efx/src/driver/toggle.js
The WebBlocks DSL allows one to re-open an existing block.
In the case of the Efx library, the author did not want to impose jQuery as part of the block's build process, in case the user is included it elsewhere; however, in the case of our particular app, we may want to bundle the two together.
Looking at our registry, we see that, although jQuery is not being included as part of the actual build, it is defined as a bower dependency:
$ blocks inspect bower_registry
efx
/Users/ebollens/Sites/test/bower_components/efx
jquery
/Users/ebollens/Sites/test/bower_components/jquery
Since jQuery doesn't have a Blockfile.rb
in it's repository, we'll start by adding that definition to Blockfile.rb
:
include 'efx', 'driver', 'tabs'
include 'efx', 'driver', 'toggle'
block 'jquery', :path => 'bower_components/jquery/dist' do
js_file 'jquery.js'
end
Next, also within Blockfile.rb
, we'll open the efx
block and add the dependency:
include 'efx', 'driver', 'tabs'
include 'efx', 'driver', 'toggle'
block 'jquery', :path => 'bower_components/jquery/dist' do
js_file 'jquery.js'
end
block 'efx' do
dependency framework.route 'jquery'
end
Inspecting shows this has the intended effect:
$ blocks inspect dependency_order
/Users/ebollens/Sites/test/bower_components/jquery/dist/jquery.js
/Users/ebollens/Sites/test/bower_components/efx/src/engine.js
/Users/ebollens/Sites/test/bower_components/efx/src/driver/tabs.css
/Users/ebollens/Sites/test/bower_components/efx/src/driver/tabs.js
/Users/ebollens/Sites/test/bower_components/efx/src/driver/toggle.css
/Users/ebollens/Sites/test/bower_components/efx/src/driver/toggle.js
At any block level, you can register a new facade into the DSL:
block 'efx' do
register_facade :file_name_block, ::WebBlocks::Facade::FileNameBlock
file_name_block 'engine', :required => true do
loose_dependency framework.route 'jquery'
end
end
This registration is available to child nodes as well:
block 'efx' do
register_facade :file_name_block, ::WebBlocks::Facade::FileNameBlock
# ..
block 'driver', :path => 'driver' do
dependency efx.route 'engine'
file_name_block 'accordion'
file_name_block 'tabs'
file_name_block 'toggle'
end
end
Resolution of the facade occurs by way of closest parent. For example, Ex2 wins over Ex1 for the sub-block:
block 'top-level' do
register_facade :ex, Ex1
block 'driver', :path => 'driver' do
register_facade :ex, Ex2
ex 'Ex2_is_used_as_handler_here'
end
end
- WebBlocks::Facade::FileNameBlock takes the name passed to it and finds CSS, JS and SCSS files by the same name and loading them into a block by that name.
- WebBlocks::Facade::RecursiveFileNamesBlock takes the name passed to it and finds all directories, making sub-blocks of them and calling this recursively, while also finding all CSS, JS and SCSS files by the same name and loading them into a block by that name.
See the demo
folder for a example setup.