Via one of those mental leaps that one can only be achieved by working in two languages at once, I had found my way to Typescript.
I was excited about putting some rigour around my code. But as I sought to install the necessary dependencies, I started hitting versioning issues. I couldn't move forward with modern packages without addressing the dinosaur in the room, Gulp 3.9.1.
The first step was to update Gulp, so I could see what was broken:
yarn upgrade --latest
I updated everything, because I wanted to be running the newest and most compatible stack.
Note: The --latest
flag tells Yarn to get the latest versions from NPM, then updates package.json
with these, so that the existing n.n.n
version limits are overwritten.
The Gulp Github page provided instructions on how to Use latest JavaScript version in your gulpfile.
That wording was important.
- I was installing Babel to allow the Gulp script to run error-free inside Nodejs
- I wasn't installing Babel to transpile project files (although I do use it for the latter via
gulp-babel
)
The instructions contained three steps:
You can write a gulpfile using a language that requires transpilation, like TypeScript or Babel, by changing the extension on your
gulpfile.js
to indicate the language and install the matching transpiler module.For Babel, rename to
gulpfile.babel.js
and install the@babel/register
module.
To see what effect this had, I reverted the file name to gulpfile.js
, after my migration.
Running a previously working task gave me this eror:
/Volumes/DanBackup 1/Websites/wpdtrt-plugin-boilerplate/gulpfile.js:27
import { series } from 'gulp';
^
SyntaxError: Unexpected token {
Renaming the file back to gulpfile.babel.js
resolved this.
Note: Don't forget to update any references to gulpfile.js
, e.g. .eslintrc
overrides
.
Because my Gulp file transpiles Front End JS from ES6 to ES5, my package.json
already contained babel-core
and babel-preset-env
.
Were these the same as @babel/core
and @babel/preset-env
?
No,
babel-
is the syntax for Babel version<7
@babel
is the scoped syntax, for Babel version7+
More information:
- The difference between babel-core and @babel/core
- "Scopes are a way of grouping related packages together"
- Babel 7 has "no more issues with accidental/intentional name squatting, a clear separation from community plugins, and a simpler naming convention"
I removed "babel-core": "^6.26.3"
. I'll find out if I still need it as I proceed with testing.
What about @babel/register
?
babel-register
adds support for ES6 modules:import foo from ./foo
- This package was the glue for my final modular solution
I also already had a .babelrc
.
- It already contained a preset called
env
- I replaced this with
@babel/env
- I also updated the
presets
reference in my separate transpile task:return src( sources.js, { allowEmpty: true } ) .pipe( babel( { presets: [ '@babel/env' ] } ) ) .pipe( rename( { suffix: '-es5' } ) ) .pipe( dest( targets.js ) );
Heading over to StackOverflow, I found Gulp error: gulp.hasTask is not a function.
There were some red herrings here about local vs global installs of Gulp, but the key takeaway was that:
gulp v4 has breaking changes and that creates some problems with
run-sequence
package... try to use try to use
gulp.series
andgulp.parallel
with yourgulp
tasks instead ofrun-sequence
If you're not familiar with run-sequence
, it:
Runs a sequence of gulp tasks in the specified order. This function is designed to solve the situation where you have defined run-order, but choose not to or cannot use dependencies.
This was intended to be a temporary solution
The Gulp 4 way has a very similar syntax, but works without the extra package:
const runSequence = require( 'run-sequence' );
gulp.task( 'myTaskGroup', ( done ) => {
runSequence(
'task1',
'task2',
'task3',
'task4',
done
);
} );
import { series } from 'gulp';
series(
task1,
task2,
task3,
task4
);
So now my tasks were in series
, but they were just firing off without returning anything.
With run-sequence
gone, my flow control was in ruins.
I headed back to StackOverflow and found this gem: Gulp error: The following tasks did not complete: Did you forget to signal async completion?.
Old:
function github( done ) {
return ghRateLimit( {
token: getGhToken()
} ).then( ( status ) => {
log( 'Github API rate limit:' );
log( `API calls remaining: ${status.core.remaining}/${status.core.limit}` );
log( ' ' );
} );
}
New:
function github( done ) {
return ghRateLimit( {
token: getGhToken()
} ).then( ( status ) => {
log( 'Github API rate limit:' );
log( `API calls remaining: ${status.core.remaining}/${status.core.limit}` );
log( ' ' );
done();
} ).catch( err => {
console.error( err );
done();
} );
}
Old:
const dummyFile = './README.md';
function wpUnit() {
const boilerplate = isBoilerplate();
const boilerplatePath = getBoilerplatePath();
const dbName = `${pluginNameSafe}_wpunit_${Date.now()}`;
const wpVersion = 'latest';
let installerPath = 'bin/';
if ( !boilerplate ) {
installerPath = `${boilerplatePath}bin/`;
}
return gulp.src( dummyFile, { read: false } )
.pipe( shell( [
`bash ${installerPath}install-wp-tests.sh ${dbName} ${wpVersion}`
] ) );
}
New:
function wpUnit() {
const boilerplate = isBoilerplate();
const boilerplatePath = getBoilerplatePath();
const dbName = `${pluginNameSafe}_wpunit_${Date.now()}`;
const wpVersion = 'latest';
let installerPath = 'bin/';
if ( !boilerplate ) {
installerPath = `${boilerplatePath}bin/`;
}
const { stdout, stderr } = await exec( `./vendor/bin/phpunit --configuration ${boilerplatePath()}phpunit.xml.dist` );
console.log( stdout );
console.error( stderr );
}
First up, I refactored the file to use export
- @timothyis put together this super helpful Gulp 4, ES6 gulpfile example gist, which I referred to often
I'd also read somewhere that large Gulpfiles would be better split into smaller modules. After reading Gulp's advice that task()
isn't the recommended pattern anymore - export your tasks and there export
laden instructions on Creating Tasks, I misunderstood this as encouragement to 'export' my file into smaller modules.
I'd done this in the past with my Gruntfile, so I went ahead and did that, though it really wasn't necessary.
- Peter Chang's [es6] import, export, default cheatsheet was helpful here
- There was also a fair amount of experimentation to get the correct (working) syntax
/**
* File: gulp-modules/documentation.js
*
* Gulp tasks to generate documentation.
*/
import { series, src } from 'gulp';
import shell from 'gulp-shell';
// internal modules
import taskHeader from './taskheader';
// constants
const dummyFile = './README.md';
/**
* Group: Tasks
* _____________________________________
*/
/**
* Function: naturalDocs
*
* Generate JS & PHP documentation.
*
* Returns:
* A stream - to signal task completion
*/
function naturalDocs() {
taskHeader(
'5a',
'Documentation',
'Documentation',
'Natural Docs (JS & PHP)'
);
// Quotes escape space better than backslash on Travis
const naturalDocsPath = 'Natural Docs/NaturalDocs.exe';
// note: src files are not used,
// this structure is only used
// to include the preceding log()
return src( dummyFile, { read: false } )
.pipe( shell( [
`mono "${naturalDocsPath}" ./config/naturaldocs`
] ) );
}
export default series(
naturalDocs
);
/**
* File: gulpfile.js
*
* Gulp build tasks.
*
* Note:
* - See package.json for scripts, which can be run with:
* --- bash
* yarn run scriptname
* ---
*/
import { series } from 'gulp';
// internal modules
import documentation from './gulp-modules/documentation';
export { documentation as documentation };
/*
* Export the default task
*
* Example:
* --- bash
* gulp
* ---
*/
export default series( documentation );
Note: I realise that the single series
are redundant.
What was more important was to realise that even after import
ing a module, I still had to export
the function reference in order to make it callable via the gulp
default task, and directly via gulp taskName
.
This may be related to Forward References, although I am already using named functions, but perhaps not for the series()
.
Thus, this issue was also resolved.
When the globs argument can only match one file (such as foo/bar.js) and no match is found, throws an error with the message, "File not found with singular glob". To suppress this error, set the
allowEmpty
option totrue
.
This is a breaking change in Gulp 4 and highlights the current lack of Migration documentation on the Gulp website.
Still, they did provide a solution, but I found their advice confusing.
For the most part, I fed arrays of globs into my tasks, not 'singular globs'. It seemed that glob arrays failed if any of their items didn't find a match.
Adding { allowEmpty: true }
resolved this issue:
// constants
const sources = {
// note: paths are relative to gulpfile, not this file
js: [
'./js/frontend.js',
'./js/backend.js',
`./${boilerplatePath()}js/frontend.js`,
`./${boilerplatePath()}js/backend.js`
]
};
const targets = {
// note: paths are relative to gulpfile, not this file
css: './css',
js: './js'
};
/**
* Function: js
*
* Transpile ES6+ to ES5, so that modern code runs in old browsers.
*
* Returns:
* A stream to signal task completion
*/
function js() {
taskHeader(
'3a',
'Assets',
'Transpile',
'ES6+ JS -> ES5 JS'
);
return src( sources.js, { allowEmpty: true } )
.pipe( babel( {
presets: [ '@babel/env' ]
} ) )
.pipe( rename( {
suffix: '-es5'
} ) )
.pipe( dest( targets.js ) );
}
This was caused by introducing the modernised flow control, to address Issue #2. Tasks in series not waiting for one another
ReferenceError: regeneratorRuntime is not defined
at _yarn (/Volumes/DanBackup/Websites/wpdtrt-plugin-boilerplate/gulp-modules/dependencies.js:207:3)
at yarn (/Volumes/DanBackup/Websites/wpdtrt-plugin-boilerplate/gulp-modules/dependencies.js:201:16)
at bound (domain.js:402:14)
at runBound (domain.js:415:12)
at asyncRunner (/Volumes/DanBackup/Websites/wpdtrt-plugin-boilerplate/node_modules/async-done/index.js:55:18)
at process._tickCallback (internal/process/next_tick.js:61:11)
From: Babel 7 - ReferenceError: regeneratorRuntime is not defined:
@babel/polyfill
🚨 As of Babel 7.4.0, this package has been deprecated in favor of directly including
core-js/stable
(to polyfill ECMAScript features) andregenerator-runtime/runtime
(needed to use transpiled generator functions):
yarn add core-js --dev
yarn add regenerator-runtime --dev
Then in my Gulpfile:
import 'core-js/stable';
import 'regenerator-runtime/runtime';
TODO: I'll need to run my earlier code to see what other issues I had.