Skyrocket

your Old Toolchain
Into the Future!

Eindhoven Developers Meetup

new Date(2017, 1, 22, 20, 15, 0);

Gaya Kessler

On the menu today

  • Toolchains: The Old versus The New
  • What we wanted to solve
  • Steps we took
  • Where we are now

What is the "old" toolchain?

  • Backend-first
  • Grunt / Gulp
  • LESS / SASS by Grunt / Gulp
  • AMD (Require.js) or Concatenation
  • jQuery like approach

Getting assets

  • Get JavaScript from script tags
  • Get Stylesheets from link tags
  • Get images from img tags
  • Require JavaScript files through injection

What is the "new" toolchain?

  • Single Page Applications
  • Backend is API
  • ES6+
  • NPM scripts for tasks
  • webpack for bundling entry points
  • JavaScript as a starting point

Getting assets

  • Get JavaScript from script tags
  • Webpack tells to include style
  • Webpack loads images
  • Require JavaScript files through webpack chunks

Why going forward is hard

  • Don't know where to start
  • Moving over (forgotten) paradigms
  • Backend is not supported yet
  • You have to figure out everything
  • You have a lot of code to manage

Why starting from scratch is easier

  • Clean slate
  • Freedom
  • No legacy
  • You can try out everything
  • You can adjust your backend to it
  • Project is most likely smaller

How the "old" way stinks

  • Gets messy really quick
  • AMD (Require.js) has better alternatives
  • Dependency management is unclear
  • Pipeline can be confusing
  • Support is getting worse

What we needed to solve at JouwWeb

New

  • Managealbe code base
  • ES6+
  • ES7 modules + chunks
  • Source maps for development

What we needed to solve at JouwWeb

Keep

  • Make sure old libs keep working
  • Adjust as little as possible
  • AMD should work too
  • Development "watch" mode
  • Production output using webpack
  • File size

"Don't pounce on every chance to rewrite the world. Refactor little bits as you go. Make it work, first. Then make it right."

- Eric Elliott

Know problems

  • What are entry points?
  • Source was copied from a weird folder structure
  • Backend injects script tags
  • Dynamic requires in our code
  • We required 3th party libs using require('lib/jquery');
  • Some dependencies couldn't be bundled
  • Files copied from node_modules using grunt

Set deliverables

Try to ship as little as possible

Steps we took

  1. Organize code in structure webpack will understand
  2. Have all our entry points work with webpack
  3. Introduce Babel
  4. Solve require('lib/jquery'); problem
  5. Solve dynamic requires
  6. Let JavaScript handle injected scripts
  7. Fix broken "old" libraries
  8. Have a development "watch" environment

Steps we didn't take

  1. LESS was still part of the "old"
  2. Fix our folder structure
  3. Handle copying of assets
  4. Make it blazing fast

Basics

We created a JavaScript file...

Execute in NPM


// package.json
{
	...
	"scripts": {
		"build": "node ./tasks/webpack"
	}
}

// cli

> npm run build

Organising the structure

module/Text/assets/js/plugin/text.js module/Video/assets/js/plugin/video.js

require('plugin/text.js'); require('plugin/video.js');

Organising the structure

module/*/assets/js/**/*.js
copy to
public/assets/js/**/*.js

Make public/assets/js a "module root" in webpack

require('filename.js');


const config = {
	...
	resolve: {
		modules: [
			'public/assets/js',
			'node_modules',
			'bower_components',
		],
	},
};

How we made "watch" work

webpack watches

public/assets/js/**/*.js

We watch for changes in

module/*/assets/js/**/*.js

Copy all to

public/assets/js/**/*.js

webpack reruns

It's not elegant, but effective

Entry points


const config = {
	...
	entry: {
		'js/initialize': './js/app/initialize',
	},
	output: {
		path: 'public/assets/',
		filename: '[name].js',
	}
}

// 'public/assets/' + 'js/initialize' + '.js'

require('lib/*');


// lib/bootstrap.js (AMD style)
define(
	['bootstrap/dist/js/bootstrap'],
	function (bootstrap) => { return bootstrap }
);

// lib/bxslider.js (CommonJS style)
module.exports = require('bxslider-4/dist/jquery.bxslider');

¯\_(ツ)_/¯

Dynamic module includes


define(['scripts/backend'], function(module) { ... });

function includeModule(name) {
	define([name], function(module) { ... });
}

// somewhere else
includeModule('scripts/backend');

function includeModule(name) {
	function cb(module) { ... }

	switch (name) {
		case 'scripts/backend':
			require(['scripts/backend'], cb);
			break;
		case 'scripts/editor':
			require(['scripts/editor'], cb);
			break;
	}
}

Injected scripts

What if the backend generates <script /> tags?


<script src="assets/js/module-a.js" />
<script src="assets/js/module-b.js" />

<script>
	window.scripts = ['module-a', 'module-b'];
</script>

// in JavaScript
window.scripts.forEach(includeModule);

Old dependencies

  • Fixed them by hand
  • Not really important
  • tinymce is a totally different story

What we did later

  • Fixed our folder structure
  • Got LESS (and other assets) to webpack
  • Hot Reloading with webpack dev server
  • Able to refactor a lot
  • SUPER FAST!

Where we want to be

  • Single Page Application
  • Mostly built on React
  • Dynamic route loading
  • Drinking beer on a sunny island

Improve your project,
just do it
one little step
at the time.

- Gaya Kessler

Thanks!

For more brain farts check @GayaNinja

(p.s. we're always looking for people, so come chat)