Kotsu
Kotsu copied to clipboard
Development, staging and production workflow
Well, I can't remember how we made such conclusions and resulted in current implementation, but after looking at this right know, I have feeling that it's strongly f**ked up.
Determination of environment
Right now we have following rules in Gruntfile, which determinating is current environment staging or production:
production: process.env.PRODUCTION or grunt.cli.tasks.includes('build')
staging: process.env.STAGING or grunt.option('staging')
So, to trigger production environment, you need to set specific env variable or use grunt build
.
For stating you need to set specific env variable or add --staging
flag.
If all of them false, assumed that environment is development. Note that you can use grunt --staging
to build staging environment locally.
Usage
Let's look where and how those values used in our current projects.
Please, carefully read how declarations are written — sometimes they seems to be... unexpected or unclear.
Let's check the examples how we use them:
In templates
-
To hide parts of UI, which should never be shown on production, but be visible on dev or staging env, like Styleguide section:
{% if env.staging or not env.production %} {% call(depth) NavItem('/styleguide') %}{% endcall %} {% endif %}
We are forced to add excessive "please, show this on staging" instead of just "show this on everything, but production", because right now we push to staging server
grunt build
, which automatically says that it is production environment. Thus we should clarify, that if it's production build, but staging server, this item shouldn't be shown -
To hide unfinished parts on production, but show them in development env:
{% if env.staging or not env.production %} {{ components.Images(...) }} {% endif %}
-
To determinate to which CSS or JS file we should link, since name will change depending on type of Grunt build:
<link rel='stylesheet' href='/{{ path.styles }}/{{ 'style.min.css' if env.production else 'style.prefixed.css' }}'>
-
To dissallow indexing in
robots.txt
for not production environment:Disallow: {{ '/' if env.staging }}
In Grunt itself
-
To determinate ending name of output of some tasks, like extension for Sass:
ext: if @config('env.production') then '.min.css' else '.prefixed.css'
The issue
I clearly see that there are few global issues here:
- Rules for determination of environment, declared in Gruntfile, are wrong.
- Rules did not clearly take into account the fact, that some environment should live on separate branch.
- Because of previous issues, in most cases usage is wrong too, or often very confusing.
Expected system
Before diving deeper into problem, let's try to determinate what we want to achieve:
-
dev
— development environment. This is where most part of work happens.Under development environment we can assume two things:
- Separate development branch
- Work in separate feature branches with following PR
-
staging
— this is environment, in which code should be in form ofrelease candidate
, as close to production-ready state as possibly.In other words, when people look at staging environment, they should see
next
version, which will be released in that state to production. It mostly used for Quality Assurance.Under staging we can assume few things, depending on scale of project:
- Released to staging server development branch with flag
--production
. Though, it isn't entirely correct approach, but it will probably work for most cases. - Standalone staging branch released to standalone server, in which manually merged ready changes from development branches.
- Released to staging server development branch with flag
-
production
— pretty much self explaining. Released to publicity version, standalone branch in which manually merged completely ready features or fixes.
We also expect to have following possibilities:
- While doing local development, to have ability build project in non-
production
(development) mode - While doing local development, to have ability build project in
production
mode
What's wrong with current system
Let's gradually find out:
-
This declaration makes too much assumptions:
production: process.env.PRODUCTION or grunt.cli.tasks.includes('build')
It assumes, that when we make
grunt build
, we always want to make production build, thus it makes check viagrunt.cli.tasks.includes('build')
. This isn't true.Sometimes we want to make full build, with all optimizations, but still see everything that we would normally see in development mode — hidden sections, temporary hidden for production unfinished UI parts, etc.
This means that
grunt.cli.tasks.includes('build')
have to be changed togrunt.option('production')
.In other words, to trigger exactly production env, we need to explicitly set env variable or use
--production
flag. -
Usage of
production.env
for determinating things like CSS or JS final file nameext: if @config('env.production') then '.min.css' else '.prefixed.css'
and
<link rel='stylesheet' href='/{{ path.styles }}/{{ 'style.min.css' if env.production else 'style.prefixed.css' }}'>
is wrong assumption, because of statement above.
This behaviour should be ruled by whether it is
grunt build
, not byproduction
flag, since as already said, sometimes we might want to makegrunt build
in not production env.Unfortunately, right know I don't see better solution then adding additional ENV variable:
env: build: grunt.cli.tasks.includes('build')
And snippets above changed to
-ext: if @config('env.production') then '.min.css' else '.prefixed.css' +ext: if @config('env.build') then '.min.css' else '.prefixed.css'
and
-<link rel='stylesheet' href='/{{ path.styles }}/{{ 'style.min.css' if env.production else 'style.prefixed.css' }}'> +<link rel='stylesheet' href='/{{ path.styles }}/{{ 'style.min.css' if env.build else 'style.prefixed.css' }}'>
-
staging
env variable:staging: process.env.STAGING or grunt.option('staging')
Seems to be excessive and not needed.
There are few reasons for it:
-
Most likely when you say something like
Disallow: {{ '/' if env.staging }}
or
{% if env.staging or not env.production %} {{ components.Images(...) }} {% endif %}
you want to simply say "show this anytime, except production", thus it will be enough to say just
-Disallow: {{ '/' if env.staging }} +Disallow: {{ '/' if not env.production }}
-{% if env.staging or not env.production %} +{% if not env.production %} {{ components.Images(...) }} {% endif %}
-
Staging isn't very specific environment. I think that it is just a special branch, to which merged development branch for QA, or, as already mentioned, that even can be (for simpler projects) development branch, deployed with
grunt build --production
. But in both cases it's purpose just to show how code will look like before deploying to production, so there shouldn't be any "tricks" when something hidden for production, but shown on staging.
However, there is little controversial part. For instance, Styleguid section. Should it be seen on staging too?
In most cases we can't afford to have 3 servers (remote development, where Styleguide will be seen, and staging and prod where it will be hidden), so staging seems like a single place for us to show Styleguide link (since we can't do that on prod). I see this as the only justification for saving this flag right now.
Besides, I'm not sure that we will be able to remove this variable, because of already mentioned issue in the begining:
If we push something to staging, it should look like production? Right? Thus we will add
--production
flag or set production env variable.But this means that this check:
Disallow: {{ '/' if not env.production }}
won't pass, and site will be allowed for indexing on staging.
Hm, seems like I remembered why we introduced
staging
variable at first place... so, after all we won't be able to remove it, thus leaving some parts of code a bit obscure... -