Skip to content
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
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.taskGroup({ 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
Copy link
Contributor

Choose a reason for hiding this comment

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

So this check is gone now. In the new code, seems like omitting a key/blockname (e.g. copy) from a project config would cause projectsWithBlock to return an empty array in blueprint.taskGroup, which would then define a gulp.task that simply executes an empty set of sub-tasks—effectively a no-op. Is that behavior drastically different from this special no-op case above that used to be here? Was it ever particularly helpful or necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

blueprint.task always generated a root task that depended on corresponding tasks in other projects. so copy-files-docs depended on (the existence of) copy-files-core, which is why we had to add the no-op shortcut. this was awkward because there wasn't actually a dependency between the copy tasks (like, they're completely separate operations), whereas there is a real dependency for the typescript tasks (core must be compiled before datetime).

in this new world, the copy task simply ignores the depTaskNames argument and voila, packages can be copied without any dependencies, as they should be!

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`));
});
});
};
50 changes: 26 additions & 24 deletions gulp/dist.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,41 @@ 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.taskGroup({
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();
}
blueprint.taskGroup({
block: "all",
name: "test-dist",
}, (project, taskName) => {
gulp.task(taskName, () => {
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();
}
});
});
});
});
return Promise.all(promises);
}

blueprint.projects.forEach((project) => {
gulp.task(`test-dist-${project.id}`, () => testDist(project));
return Promise.all(promises);
});
});
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", () => (
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the motivation behind this -gulp-suffixed tslint task as distinct from the non-suffixed tslint task? Looks like we're tslint-ing all JS files...and then also JS files within gulp/? Is that necessary on non-typescript files?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok so: the previous iteration of this task ran tslint on all .js files (hence the package/**/*.js glob).

this new iteration runs tslint on the gulp code only because JS files in packages are now linted by tslint-<package>.

Copy link
Contributor

Choose a reason for hiding this comment

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

Gotcha 👍

gulp.src(["*.js", "gulp/**/*.js"])
.pipe(plugins.tslint({ formatter: "verbose" }))
.pipe(plugins.tslint.report())
.pipe(plugins.count("## javascript files linted"))
Expand Down
37 changes: 27 additions & 10 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,17 +45,32 @@ 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
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of "block", I propose another name: "scope"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

love it!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

actually i'm less into scope now. config block sounds better than config scope, as does projectsWithBlock vs projectsWithScope.

i am open to a new name though, just don't think we've found it yet.

Copy link
Contributor

Choose a reason for hiding this comment

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

block really doesn't mean anything. what do you mean by "config block sounds better than config scope"? also projectsWithScope sounds fine to me

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what is a scope?

* @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("-"));
taskGroup(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("-"));
Copy link
Contributor

Choose a reason for hiding this comment

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

It appears that dep and id both project IDs in practice (e.g. "core", "datetime", "table"). It'd be helpful to name dep to depId IMO, just so it's clear that the names we're building on this line and the line above both follow the same scheme.

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);

Expand Down
13 changes: 8 additions & 5 deletions gulp/isotest.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
"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.taskGroup({
block: "isotest",
parallel: false,
}, (project, taskName) => {
gulp.task(taskName, [`typescript-compile-${project.id}`], () => {
return gulp.src(project.cwd + "test.iso/**/*")
Copy link
Contributor

Choose a reason for hiding this comment

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

@giladgray just gonna comment on every little thing until I build up a more solid understanding of this whole build system. There's a minor change in behavior here, yes (${project.cwd}/test/isotest.js => ${project.cwd}/test.io/**/*)? Is the reasoning that we expect to add more isomorphic test files later, so might as well support anything in that directory moving forward?

.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.taskGroup({
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));
};
40 changes: 25 additions & 15 deletions gulp/sass.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,31 @@ module.exports = (blueprint, gulp, plugins) => {
],
};

blueprint.task("sass", "lint", [], (project, isDevMode) => (
gulp.src(config.srcGlob(project))
.pipe(plugins.stylelint({
failAfterError: !isDevMode,
reporters: [
{ formatter: "string", console: true },
],
syntax: "scss",
}))
.pipe(plugins.count(`${project.id}: ## stylesheets linted`))
));
blueprint.taskGroup({
block: "sass",
name: "stylelint",
}, (project, taskName) => {
gulp.task(taskName, () => (
gulp.src(config.srcGlob(project))
.pipe(plugins.stylelint({
failAfterError: true,
reporters: [
{ formatter: "string", console: true },
],
syntax: "scss",
}))
.pipe(plugins.count(`${project.id}: ## stylesheets linted`))
));
});

blueprint.taskGroup({
block: "sass",
}, (project, taskName, depTaskNames) => {
gulp.task(taskName, ["icons", "sass-variables", ...depTaskNames], () => sassCompile(project, false));
gulp.task(`${taskName}:only`, () => sassCompile(project, true));
});

blueprint.task("sass", "compile", ["icons", "sass-variables"], (project, isDevMode) => {
function sassCompile(project, isDevMode) {
const sassCompiler = plugins.sass({
importer: packageImporter({ cwd: project.cwd }),
});
Expand Down Expand Up @@ -87,7 +99,7 @@ module.exports = (blueprint, gulp, plugins) => {
.pipe(gulp.dest(blueprint.destPath(project)))
// only bundled packages will reload the dev site
.pipe(project.sass === "bundle" ? plugins.connect.reload() : plugins.util.noop());
});
}

// concatenate all sass variables files together into one single exported list of variables
gulp.task("sass-variables", ["icons"], () => {
Expand Down Expand Up @@ -115,6 +127,4 @@ module.exports = (blueprint, gulp, plugins) => {
.pipe(plugins.insert.append(".unit-test { width: @pt-grid-size * 2; }"))
.pipe(plugins.less());
});

gulp.task("sass", ["sass-lint", "sass-compile"]);
};
33 changes: 21 additions & 12 deletions gulp/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,28 @@ module.exports = (blueprint, gulp, plugins) => {
project.typescriptProject = createTypescriptProject(tsconfig);
});

const lintTask = (project, isDevMode) => (
gulp.src(path.join(project.cwd, "!(dist|node_modules|typings)", "**", "*.ts{,x}"))
.pipe(plugins.tslint({ formatter: "verbose" }))
.pipe(plugins.tslint.report({ emitError: !isDevMode }))
.pipe(plugins.count(`${project.id}: ## typescript files linted`))
);
// Lint all source files using TSLint
blueprint.task("typescript", "lint", [], lintTask);
gulp.task("typescript-lint-docs", () => lintTask(blueprint.findProject("docs"), false));
gulp.task("typescript-lint-w-docs", () => lintTask(blueprint.findProject("docs"), true));
blueprint.taskGroup({
block: "all",
name: "tslint",
}, (project, taskName) => {
gulp.task(taskName, () => (
gulp.src(path.join(project.cwd, "!(dist|node_modules|typings)", "**", "*.{js,jsx,ts,tsx}"))
.pipe(plugins.tslint({ formatter: "verbose" }))
.pipe(plugins.tslint.report({ emitError: true }))
.pipe(plugins.count(`${project.id}: ## files tslinted`))
));
});

blueprint.taskGroup({
block: "typescript",
name: "tsc",
}, (project, taskName, depTaskNames) => {
gulp.task(taskName, ["icons", ...depTaskNames], () => typescriptCompile(project, false));
gulp.task(`${taskName}:only`, () => typescriptCompile(project, true));
});

// Compile a TypeScript project using gulp-typescript to individual .js files
blueprint.task("typescript", "compile", ["icons"], (project, isDevMode) => {
function typescriptCompile(project, isDevMode) {
const tsProject = project.typescriptProject;

const tsResult = tsProject.src()
Expand All @@ -52,5 +61,5 @@ module.exports = (blueprint, gulp, plugins) => {
tsResult.js.pipe(plugins.sourcemaps.write(".", { sourceRoot: null })),
tsResult.dts,
]).pipe(gulp.dest(blueprint.destPath(project)));
});
}
};
5 changes: 2 additions & 3 deletions gulp/webpack.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ module.exports = (blueprint, gulp) => {
const webpackConfig = require("./util/webpack-config");

const docsProject = blueprint.findProject("docs");

const configuration = webpackConfig.generateWebpackTypescriptConfig(docsProject);

gulp.task("webpack-compile-docs", ["docs"], (callback) => {
gulp.task("webpack-docs", ["docs"], (callback) => {
webpack(configuration, webpackConfig.webpackDone(callback));
});

gulp.task("webpack-compile-w-docs", (callback) => { // eslint-disable-line no-unused-vars
gulp.task("webpack-docs-watch", (callback) => {
// rely on editor for compiler errors during development--this results in _massive_ speed increase
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we not need the eslint-disable line anymore? Looks like we're still not using the callback argument anywhere. Did we need that comment in the first place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we are not using eslint anymore! it's all TSLint

configuration.ts.transpileOnly = true;
// never invoke callback so it runs forever!
Expand Down