[MUSIC]
Hello. My name is Christopher Allen and I joined the Blockly team last year to
help with the work the team is doing modernising the Blockly codebase. As part of that work,
I’ve been looking at how blocks are defined and at the library of predefined blocks that
Blockly provides for common programming constructs like if statements, loops, variables and so on.
I’ve been discovering the history of how blocks are defined, and thinking about
how it might change in the future. This talk is going to be part archeology
and part futurology: I will share with you a little of what I have learned, talk a bit
about where we are now, and share some of our thoughts about possible plans for the future.
Let’s start by looking at some examples of how blocks have been defined in the past.
Neil Fraser was kind enough to provide me with some copies of the Blockly codebase
that predate the earliest commits in our current Git repository. The first two versions he has,
dating from 2011, don’t have any predefined blocks at all, just a very early version
of the block factory tool that allows one to define a block interactively.
The first recognisable library of block definitions appears in a version from March 2012.
Here is an excerpt from the file language_core.js.
There are a few things about this code that I find interesting:
There is no module system in use yet. This script just tacks a namespace object onto the
root Blockly namespace object, and then creates block definitions as properties on that object.
Those block definitions are just plain object literals with a bunch of properties. The
constructor the Block class—not shown here—takes a block name string and looks it up in the
Blockly.Language namespace, to get one of these definitions, which it describes as “prototypes”,
though in fact they are used as mixins: the properties defined here get copied onto the
freshly-created Block object. This is largely how block definitions continue to work today.
You will note that so far we have just two properties to mix in: one is the init function
that you may be familiar with; the other is part of the JavaScript code generator.
The init function itself sets up the newly-created block by calling various
methods to set the colour, title text and inputs. The block is defined in code.
By the way: at this time blocks did not yet have fields as such,
although the init function could set a flag to allow the user to edit the block's title,
giving the block a single ersatz string-valued field.
Here is an excerpt from the first version of Blockly to appear in our current git repository;
at this time Blockly development was still being done in an SVN repository on Google Code,
but Ellen Spertus had begun mirroring it
to git, and a year later this would become the official GitHub repo for the project.
Compared to the previous version, there have been a few changes:
Blockly is now using the Closure module system, with goog.provide. This isn’t
conceptually different from the manually-created namespace objects of the previous version,
but it meant the project could be compiled with the Closure Compiler.
The block definitions now live in the Blockly.Blocks definitions dictionary.
The “if” block definition now comprises quite a number of different methods—not
just init—but notably there is one that is missing: the toJavaScript method is gone,
with all the JavaScript generation code having been moved to a separate module entirely. This
meant that Blockly could easily support generating code in many different languages
from the same set of block definitions. The init method is now using strings from
Blockly.Msg, meaning that we have support for localisation into different languages.
One key feature of this is that the actual layout of the block—what order the various inputs,
labels, fields and so on appear in—can be modified by the translators, rather than being
hard-coded into the block definition itself. But overall our our block definition still
consists of a mixin object with some methods, in which the init method sets things up by
calling various methods on the Blockly object. The objects are still being defined in code.
The definition of our “if” block doesn’t change all that that much for the next three years,
but in February 2017 a commit by Andrew Marshall arrives that dramatically revises its definition.
Gone is the previous mixin object; instead, the block is defined by passing an array of very
simple object literals—without any methods—to a new function, defineBlocksWithJsonArray.
The reason for this change was that Blockly was growing in popularity and efforts had
begun to write native ports of Blockly for Android—in Java—and iOS—in Swift.
Replacing the previous procedural block definitions with this new
declarative style made it easier to share the definitions between the ports. To that end,
you will note that the block definitions are written in a JSON-compatible syntax,
with double-quoted property names and so on; this mean that they could be extracted
as JSON text and used, verbatim, in the other ports, and you will see some special comments
here to help the extractor script find the correct parts of the file to extract.
Defining new blocks still needed some actual code in most cases, of course,
but now the code was separated out into extensions. Here we see the “controls_if”
block definition including a request for the corresponding “controls_if” extension.
The extensions would need to be ported to native code for each platform, of course,
but as much as possible of the block definition was now in a platform-neutral, declarative format.
Now, let’s have a look at a few changes that have been made in the most recent releases of Blockly.
Compared to the version we just saw from five years ago, there are only
a few notable changes in this snippet, but each of them point towards our future plans.
We’ve still using the Closure module system, but we’ve moved from goog.provide modules,
which are basically namespace objects without their own scope, to goog.module, which has
semantics much closer to those of other module systems like CommonJS and ES Modules. This will
facilitate the coming transition to TypeScript. We’ve also renamed the modules which contain
block definition to use the “Blockly.libraryBlocks” prefix,
to more clearly separate them from the core module that defines the Blockly.Blocks dictionary.
Instead of using defineBlocksWithJsonArray, we use a new function,
createBlockDefinitionsFromJsonArray, which digests the declarations as before,
but does not install the resulting block definitions by adding them to
the Blockly.Blocks dictionary. Instead, it just returns them.
The block definitions are then installed, using another new function, defineBlocks,
but they are also exported from the module as the named export “blocks”.
What’s this about? OK, let’s talk about…
The future of library blocks.
First: a disclaimer: everything I am about to talk about is just our current thinking,
and is subject to change based on what we learn as we continue to modernise and update
the Blockly codebase—and also depending on your feedback.
OK, let’s go.
At the moment, loading any of the library block modules causes the defined blocks to be
installed in the Blockly.Blocks dictionary. That makes it difficult to control exactly
what block definitions are available: if you want to change or modify the pre-defined set,
you have to delete or update entries in the dictionary—and if we add new blocks,
you may have to add new deletions to your code if you don’t want our additions.
To solve this problem, we’d like to make the library block modules side-effect free.
We’ve started that process by separating the creation from the installation, and by explicitly
exporting the block definitions from each of the libraryBlocks modules. We’re now considering
removing the call to defineBlocks. That will mean that if you want to use the library blocks,
you’ll have to make this call to install them yourself—but it will also mean that
you have the opportunity to pick and choose exactly which block definitions get installed.
We’re planning to do the same thing with the extensions, by the way: define and export them
in the library blocks modules, and let developers install the ones they need.
There are some things we need to think about—for example, how to ensure that the correct extensions
are installed for a given set of blocks. We’d welcome your thoughts about these plans. There’s
a bug open about this work— #5875; please add your comments and suggestions there.
So we've separated the definition of a block from the installation of that definition for use.
We've exported the blocks defined by each module from that module.
We're proposing to stop doing the installation automatically,
so developers can choose which of the exported block definitions they want to use.
That will make the library block modules side-effect free.
Once we've done that, the next obvious question is:
do they even need to exist in the core blockly repository at all,
or could we move them to plugins? This would reduce the size of the Blockly
NPM package, which would be beneficial—especially for those who don't use the library blocks at all.
It might also help us streamline development. There are technical issues, though, since there
is some integration between the library blocks and Blockly core, and the block definitions
are needed by the generators and used in various tests, including the playground.
It probably only makes sense to move the blocks to plugins if we also move the generators.
We'll need to investigate what's involved before making a decision about whether it's worthwhile.
I hope you’ve found this little tour of the history and future of block definitions
interesting. We’re looking forward to hearing your thoughts about our plans. If you have
questions you can post them in our Q&A, or bring them to Q&A sessions at the summit—and of course
we always welcome relevant comments on issues and pull requests in the Blockly repository.
Thank you.