Skip to content

Commit

Permalink
Major gulp refactor: blueprint.defineTaskGroup() (#617)
Browse files Browse the repository at this point in the history
* blueprint.defineTaskGroup gulp helper function

defines a task group based on a block query with some cool options. gulp task itself is defined in callback so it can be configured precisely as needed. this removes the need to specify deps in config.

function accepts options object to support more complex usage and multiple args.

* copy-files => copy + remove `copy: false`

* compile tasks use simpler `taskGroup`

sass-compile => sass
sass-lint => stylelint
typescript-compile => tsc
typescript-lint => tslint
webpack-compile-docs => webpack-docs
webpack-compile-w-docs => webpack-docs-watch

-w- => :only for incremental build tasks so:
sass-compile-table => sass-table:only

dependencies are much easier to trace now because they're all listed in task definition.

* remove now unused blueprint.task()
  • Loading branch information
giladgray authored Feb 8, 2017
1 parent 0c5daf8 commit 5d1997a
Show file tree
Hide file tree
Showing 18 changed files with 205 additions and 229 deletions.
3 changes: 0 additions & 3 deletions Gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ const projects = [
id: "core",
cwd: "packages/core/",
dependencies: [],
copy: false,
isotest: true,
karma: true,
sass: "compile",
Expand All @@ -65,7 +64,6 @@ const projects = [
id: "datetime",
cwd: "packages/datetime/",
dependencies: ["core"],
copy: false,
isotest: true,
karma: true,
sass: "compile",
Expand Down Expand Up @@ -111,7 +109,6 @@ const projects = [
id: "table",
cwd: "packages/table/",
dependencies: ["core"],
copy: false,
isotest: true,
karma: true,
sass: "compile",
Expand Down
8 changes: 4 additions & 4 deletions gulp/aliases.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ module.exports = (blueprint, gulp) => {
// lint all the things!
// this will fail the CI build but will not block starting the server.
// your editor is expected to handle this in realtime during development.
gulp.task("check", ["tslint", "sass-lint", "typescript-lint", "typescript-lint-docs"]);
gulp.task("check", ["tslint", "tslint-gulp", "stylelint"]);

// compile all the project source codes EXCEPT for docs webpack
// (so we can run it in watch mode during development)
gulp.task("compile", ["sass-compile", "typescript-compile", "copy-files"]);
gulp.task("compile", ["sass", "tsc", "copy"]);

// generate docs data files
gulp.task("docs", ["docs-interfaces", "docs-kss", "docs-versions", "docs-releases"]);

// perform a full build of the code and then finish
gulp.task("build", (done) => rs("clean", "compile", "bundle", "webpack-compile-docs", done));
gulp.task("build", (done) => rs("clean", "compile", "bundle", "webpack-docs", done));

// run test tasks in series to keep outputs separate
gulp.task("test", (done) => rs("test-dist", "karma", "isotest-mocha-w", done));
gulp.task("test", (done) => rs("test-dist", "karma", "isotest", done));

// compile code and start watching for development
gulp.task("default", (done) => rs("clean", "compile", "docs", "watch", done));
Expand Down
33 changes: 15 additions & 18 deletions gulp/copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,20 @@ module.exports = (blueprint, gulp, plugins) => {
var mergeStream = require("merge-stream");
var path = require("path");

blueprint.task("copy", "files", [], (project) => {
// allow for no-op on project dependencies
if (project.copy === false) {
return;
}

// copy options is a map of file globs to array of dest directories.
// given: "copy": { "path/to/file.txt": {to: ["foo/bar"], base: "path"} }
// the file at currProject/path/to/file.txt is copied to currProject/build/foo/bar/to/file.txt
return mergeStream(Object.keys(project.copy).map((key) => {
var dests = project.copy[key].to;
var base = project.copy[key].base || "";
var stream = gulp.src(path.join(project.cwd, key), { base: path.join(project.cwd, base) });
dests.forEach((dest) => {
stream = stream.pipe(gulp.dest(blueprint.destPath(project, dest)));
});
return stream;
})).pipe(plugins.count(`${project.id}: <%= files %> copied`));
blueprint.defineTaskGroup({ block: "copy" }, (project, taskName) => {
gulp.task(taskName, () => {
// copy options is a map of file globs to array of dest directories.
// given: "copy": { "path/to/file.txt": {to: ["foo/bar"], base: "path"} }
// the file at currProject/path/to/file.txt is copied to currProject/build/foo/bar/to/file.txt
return mergeStream(Object.keys(project.copy).map((key) => {
var dests = project.copy[key].to;
var base = project.copy[key].base || "";
var stream = gulp.src(path.join(project.cwd, key), { base: path.join(project.cwd, base) });
dests.forEach((dest) => {
stream = stream.pipe(gulp.dest(blueprint.destPath(project, dest)));
});
return stream;
})).pipe(plugins.count(`${project.id}: <%= files %> copied`));
});
});
};
58 changes: 33 additions & 25 deletions gulp/dist.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,47 @@ module.exports = (blueprint, gulp) => {
const webpack = require("webpack");
const webpackConfig = require("./util/webpack-config");

blueprint.projectsWithBlock("typescript").forEach((project) => {
gulp.task(`bundle-${project.id}`, (done) => {
blueprint.defineTaskGroup({
block: "typescript",
name: "bundle",
}, (project, taskName) => {
gulp.task(taskName, (done) => {
webpack(
webpackConfig.generateWebpackBundleConfig(project),
webpackConfig.webpackDone(done)
);
});
});
gulp.task("bundle", blueprint.taskMapper("typescript", "bundle"));

// asserts that all main fields in package.json reference existing files
function testDist(project) {
const pkgJson = require(path.resolve(project.cwd, "package.json"));
const promises = ["main", "style", "typings"]
.filter((field) => pkgJson[field] !== undefined)
.map((field) => {
const filePath = path.resolve(project.cwd, pkgJson[field]);
return new Promise((resolve, reject) => {
// non-existent file will callback with err; we don't care about actual contents
fs.readFile(filePath, (err) => {
if (err) {
reject(`${pkgJson.name}: "${field}" not found (${pkgJson[field]})`);
} else {
resolve();
}
});
});
const PACKAGE_MAIN_FIELDS = ["main", "style", "typings"];
blueprint.defineTaskGroup({
block: "all",
name: "test-dist",
}, (project, taskName) => {
gulp.task(taskName, () => {
const pkgJson = require(path.resolve(project.cwd, "package.json"));
const promises = PACKAGE_MAIN_FIELDS
.filter((field) => pkgJson[field] !== undefined)
.map((field) => assertFileExists(
path.resolve(project.cwd, pkgJson[field]),
`${pkgJson.name}: "${field}" not found (${pkgJson[field]})`
));
// using promises here so errors will be produced for each failing package, not just the first
return Promise.all(promises);
});
});

function assertFileExists(filePath, errorMessage) {
return new Promise((resolve, reject) => {
// non-existent file will callback with err; we don't care about actual contents
fs.readFile(filePath, (err) => {
if (err) {
reject(errorMessage);
} else {
resolve();
}
});
return Promise.all(promises);
});
}

blueprint.projects.forEach((project) => {
gulp.task(`test-dist-${project.id}`, () => testDist(project));
});
gulp.task("test-dist", blueprint.taskMapper("id", "test", "dist"));
};
4 changes: 2 additions & 2 deletions gulp/hygiene.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ module.exports = (blueprint, gulp, plugins) => {
return del(cleanDirs, { force: true });
});

gulp.task("tslint", () => (
gulp.src(["*.js", "gulp/**/*.js", "packages/*/*.js"])
gulp.task("tslint-gulp", () => (
gulp.src(["*.js", "gulp/**/*.js"])
.pipe(plugins.tslint({ formatter: "verbose" }))
.pipe(plugins.tslint.report())
.pipe(plugins.count("## javascript files linted"))
Expand Down
43 changes: 22 additions & 21 deletions gulp/icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,10 @@ module.exports = (blueprint, gulp, plugins) => {
const text = require("./util/text");
const mergeStream = require("merge-stream");

// accepts map of filename to array of lines, writes lines to file, writes to src/generated
function writeFiles(files) {
const streams = map(files, (contents, filename) => text.fileStream(filename, contents.join("\n") + "\n"));
const outputDir = path.join(blueprint.findProject("core").cwd, "src", "generated");
return mergeStream(...streams).pipe(gulp.dest(outputDir));
}
const ICONS = require(path.resolve(blueprint.findProject("core").cwd, "resources", "icons", "icons.json"));

// generate sass and typescript files containing icon variables, driven by docs/src/icons.json
gulp.task("icons", () => {
const ICONS = require(path.resolve(blueprint.findProject("core").cwd, "resources", "icons", "icons.json"));

function toEnumName(icon) {
return icon.className.replace("pt-icon-", "").replace(/-/g, "_").toUpperCase();
}
function buildTSObject(objectName, valueGetter) {
return [
// the TS files are published to NPM so they need a header, but the Sass files are compiled away
text.COPYRIGHT_HEADER,
"// tslint:disable:object-literal-sort-keys",
`export const ${objectName} = {`,
...ICONS.map((prop) => ` ${toEnumName(prop)}: "${valueGetter(prop)}",`),
"};",
];
}

return writeFiles({
// great big map for iteration
Expand All @@ -53,4 +33,25 @@ module.exports = (blueprint, gulp, plugins) => {
"iconStrings.ts": buildTSObject("IconContents", (icon) => icon.content.replace("\\", "\\u")),
});
});

// accepts map of filename to array of lines, writes lines to file, writes to src/generated
function writeFiles(files) {
const streams = map(files, (contents, filename) => text.fileStream(filename, contents.join("\n") + "\n"));
const outputDir = path.join(blueprint.findProject("core").cwd, "src", "generated");
return mergeStream(...streams).pipe(gulp.dest(outputDir));
}

function toEnumName(icon) {
return icon.className.replace("pt-icon-", "").replace(/-/g, "_").toUpperCase();
}
function buildTSObject(objectName, valueGetter) {
return [
// the TS files are published to NPM so they need a header, but the Sass files are compiled away
text.COPYRIGHT_HEADER,
"// tslint:disable:object-literal-sort-keys",
`export const ${objectName} = {`,
...ICONS.map((prop) => ` ${toEnumName(prop)}: "${valueGetter(prop)}",`),
"};",
];
}
};
39 changes: 27 additions & 12 deletions gulp/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
*/
"use strict";

const rs = require("run-sequence");
const path = require("path");
const util = require("util");
const plugins = require("gulp-load-plugins")();

/**
Expand Down Expand Up @@ -43,22 +45,35 @@ module.exports = (gulp, config) => {
},

/**
* Returns an array of task names of the format [prefix]-[...prefixes]-[project]
* for every project with the given block.
* @param block {string} name of project config block
* @param prefix {string} first prefix, defaults to block name
* @param prefixes {string[]} additional prefixes before project name
* @returns {string[]}
* Define a group of tasks for projects with the given config block.
* The special block `"all"` will operate on all projects.
* The `block` is used as the task name, unless `name` is explicitly defined.
* The `taskFn` is called for each matched project with `(project, taskName, depTaskNames)`.
* The task name is of the format `[name]-[project.id]`.
* Finally, a "group task" is defined with the base name that runs all the project tasks.
* This group task can be configured to run in parallel or in sequence.
* @param {{block: string, name?: string, parallel?: boolean}} options
* @param {Function} taskFn called for each project containing block with `(project, taskName, depTaskNames)`
*/
taskMapper(block, prefix = block, ...prefixes) {
return blueprint
.projectsWithBlock(block)
.map(({ id }) => [prefix, ...prefixes, id].join("-"));
defineTaskGroup(options, taskFn) {
const { block, name = block, parallel = true } = options;

const projects = (block === "all") ? blueprint.projects : blueprint.projectsWithBlock(block);

const taskNames = projects.map((project) => {
const { dependencies = [], id } = project;
// string name is combined with block; array name ignores/overrides block
const taskName = [name, id].join("-");
const depNames = dependencies.map((dep) => [name, dep].join("-"));
taskFn(project, taskName, depNames);
return taskName;
});

// can run tasks in series when it's preferable to keep output separate
gulp.task(name, parallel ? taskNames : (done) => rs(...taskNames, done));
},
}, config);

blueprint.task = require("./util/task.js")(blueprint, gulp);

[
"aliases",
"copy",
Expand Down
14 changes: 9 additions & 5 deletions gulp/isotest.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
"use strict";

module.exports = (blueprint, gulp, plugins) => {
const path = require("path");

blueprint.task("isotest", "mocha", ["typescript-compile-*"], (project) => {
return gulp.src(path.join(project.cwd, "test", "isotest.js"))
.pipe(plugins.mocha());
blueprint.defineTaskGroup({
block: "isotest",
parallel: false,
}, (project, taskName) => {
// be sure to run `gulp tsc` beforehand
gulp.task(taskName, () => {
return gulp.src(project.cwd + "test/isotest.js")
.pipe(plugins.mocha());
});
});
};
12 changes: 5 additions & 7 deletions gulp/karma.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
"use strict";

module.exports = (blueprint, gulp, plugins) => {
const rs = require("run-sequence").use(gulp);
const karma = require("karma");
const createConfig = require("./util/karma-config");

blueprint.projectsWithBlock("karma").forEach((project) => {
gulp.task(`karma-${project.id}`, (done) => {
blueprint.defineTaskGroup({
block: "karma",
parallel: false,
}, (project, taskName) => {
gulp.task(taskName, (done) => {
const server = new karma.Server(createConfig(project), done);
return server.start();
});
Expand All @@ -32,8 +34,4 @@ module.exports = (blueprint, gulp, plugins) => {
return server.start();
});
});

// running in sequence so output is human-friendly
// (in parallel, all suites get interspersed and it's a mess)
gulp.task("karma", (done) => rs(...blueprint.taskMapper("karma"), done));
};
Loading

1 comment on commit 5d1997a

@blueprint-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major gulp refactor: blueprint.defineTaskGroup() (#617)

Preview: docs
Coverage: core | datetime

Please sign in to comment.