Skip to content

Commit

Permalink
feat: Support Promises as config
Browse files Browse the repository at this point in the history
  • Loading branch information
danez committed Nov 6, 2021
1 parent 05cd9d2 commit 312a1fc
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 85 deletions.
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,6 @@ On the command line you can then do the following.
In some cases you might want to load the webpack config lazy. This can be done by specifying a function as the config value. The first paramter to this function will be the complete grunt config, which can be used in cases where grunt templates do not work (see below).

```js
const webpackConfig = require("./webpack.config.js");

module.exports = function (grunt) {
grunt.initConfig({
webpack: {
Expand All @@ -190,15 +188,29 @@ module.exports = function (grunt) {
};
```

You could also use a promise

```js
const webpackConfig = require("./webpack.config.js");

module.exports = function (grunt) {
grunt.initConfig({
webpack: {
myconfig: Promise.resolve(webpackConfig),
},
});

grunt.loadNpmTasks("grunt-webpack");
};
```

#### Grunt templates

grunt-webpack supports grunt templates in all string values in it's configuration.

In the next example we use a template for `output.filename`.

```js
const webpackConfig = require("./webpack.config.js");

module.exports = function (grunt) {
grunt.initConfig({
outputFileName: "output.js",
Expand All @@ -220,8 +232,6 @@ module.exports = function (grunt) {
For plugins we cannot support grunt template interpolation, as plugins are class instances which we cannot modify during runtime without breaking them. If you need to use template in a string option to a plugin, you can use lazy config loading and use the supplied config. You can also use grunt inside directly to access utility methods:

```js
const webpackConfig = require("./webpack.config.js");

module.exports = function (grunt) {
grunt.initConfig({
name: "Webpack",
Expand Down
39 changes: 32 additions & 7 deletions src/options/OptionHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,46 @@ class OptionHelper {
this.target = target;
}

generateOptions() {
const baseOptions = this.getRawConfig([this.taskName, "options"]);
preloadOptions(done) {
if (!this.options) {
this.generateOptions()
.then((options) => {
this.options = options;
done();
})
.catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
done();
});
} else {
process.nextTick(done);
}
}

async generateOptions() {
const baseOptions = await this.readRawConfig([this.taskName, "options"]);
if (Array.isArray(baseOptions)) {
throw new Error(
"webpack.options must be an object, but array was provided",
);
}

const targetOptions = await this.readRawConfig([
this.taskName,
this.target,
]);

return defaults.mergeOptions(
this.getDefaultOptions(),
baseOptions,
this.getRawConfig([this.taskName, this.target]),
targetOptions,
);
}

getOptions() {
if (!this.options) {
this.options = this.generateOptions();
throw new Error("Options need to be preloaded with `preloadOptions()`");
}

return this.options;
Expand All @@ -52,20 +74,23 @@ class OptionHelper {
return typeof option === "function" ? option(options) : option;
}

getRawConfig(ns) {
async readRawConfig(ns) {
let obj = this.grunt.config.getRaw(ns) || {};

if (typeof obj === "function") {
obj = obj(this.grunt.config.get());
}

deepForEach(obj, (value, key, parent) => {
// Might be a Promise
const options = await obj;

deepForEach(options, (value, key, parent) => {
if (typeof value === "string") {
parent[key] = this.grunt.config.process(value);
}
});

return obj;
return options;
}

filterGruntOptions(options) {
Expand Down
11 changes: 11 additions & 0 deletions src/options/__mocks__/TestOptionHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use strict";

const OptionHelper = require("../OptionHelper");

class TestOptionHelper extends OptionHelper {
getDefaultOptions() {
return {};
}
}

module.exports = TestOptionHelper;
47 changes: 47 additions & 0 deletions src/options/__tests__/OptionHelper.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import OptionHelper from "../__mocks__/TestOptionHelper";

describe("OptionHelper", () => {
test("supports functions as options", () => {
const options = () => ({ mode: "production" });
const helper = new OptionHelper({
config: {
getRaw() {
return options;
},
get() {
return options;
},
process(value) {
return value;
},
},
});

helper.preloadOptions(() => {
expect(helper.getOptions()).toMatchSnapshot();
});
});

test("supports Promise as options", () => {
const options = Promise.resolve({
mode: "production",
});
const helper = new OptionHelper({
config: {
getRaw() {
return options;
},
get() {
return options;
},
process(value) {
return value;
},
},
});

helper.preloadOptions(() => {
expect(helper.getOptions()).toMatchSnapshot();
});
});
});
8 changes: 5 additions & 3 deletions src/options/__tests__/WebpackDevServerOptionHelper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ describe("WebpackDevServerOptionHelper", () => {
const options = {};
const helper = new WebpackDevServerOptionHelper();

helper.getRawConfig = () => options;
helper.readRawConfig = () => options;

expect(helper.getWebpackOptions()).toMatchSnapshot();
expect(helper.getWebpackDevServerOptions()).toMatchSnapshot();
helper.preloadOptions(() => {
expect(helper.getWebpackOptions()).toMatchSnapshot();
expect(helper.getWebpackDevServerOptions()).toMatchSnapshot();
});
});

test("supports array config for webpack", () => {
Expand Down
13 changes: 13 additions & 0 deletions src/options/__tests__/__snapshots__/OptionHelper.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`OptionHelper supports Promise as options 1`] = `
Object {
"mode": "production",
}
`;

exports[`OptionHelper supports functions as options 1`] = `
Object {
"mode": "production",
}
`;
24 changes: 13 additions & 11 deletions tasks/webpack-dev-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,22 @@ npm install --save-dev webpack-dev-server
}

const optionHelper = new OptionHelper(grunt, this.name, target);
const opts = optionHelper.getOptions();
const webpackOptions = optionHelper.getWebpackOptions();
optionHelper.preloadOptions(() => {
const opts = optionHelper.getOptions();
const webpackOptions = optionHelper.getWebpackOptions();

const compiler = webpack(webpackOptions);
if (opts.progress) {
processPluginFactory.addPlugin(compiler, webpackOptions);
}
const compiler = webpack(webpackOptions);
if (opts.progress) {
processPluginFactory.addPlugin(compiler, webpackOptions);
}

const server = new WebpackDevServer(
optionHelper.getWebpackDevServerOptions(),
compiler,
);
const server = new WebpackDevServer(
optionHelper.getWebpackDevServerOptions(),
compiler,
);

server.startCallback(() => {});
server.startCallback(() => {});
});
});
},
);
Expand Down
118 changes: 60 additions & 58 deletions tasks/webpack.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,72 +43,74 @@ module.exports = (grunt) => {

const optionHelper = new OptionHelper(grunt, this.name, target);

const watch = optionHelper.get("watch");
const opts = {
cache: watch ? false : optionHelper.get("cache"),
failOnError: optionHelper.get("failOnError"),
keepalive: optionHelper.get("keepalive"),
progress: optionHelper.get("progress"),
stats: optionHelper.get("stats"),
storeStatsTo: optionHelper.get("storeStatsTo"),
watch,
};

const webpackOptions = optionHelper.getWebpackOptions();

const compiler = webpack(webpackOptions);

if (opts.cache) {
cachePluginFactory.addPlugin(target, compiler);
}
if (opts.progress) {
processPluginFactory.addPlugin(compiler, webpackOptions);
}

const handler = (err, stats) => {
if (err) {
done(err);
return;
}

if (opts.stats && !stats.hasErrors()) {
grunt.log.writeln(
stats
.toString(opts.stats)
// add plugin version with and without colors
.replace(
/(\n(.*)Version: webpack (.*)\d+\.\d+\.\d+(.*))\n/,
`$1$2 / grunt-webpack $3${pkg.version}$4\n`,
),
);
optionHelper.preloadOptions(() => {
const watch = optionHelper.get("watch");
const opts = {
cache: watch ? false : optionHelper.get("cache"),
failOnError: optionHelper.get("failOnError"),
keepalive: optionHelper.get("keepalive"),
progress: optionHelper.get("progress"),
stats: optionHelper.get("stats"),
storeStatsTo: optionHelper.get("storeStatsTo"),
watch,
};

const webpackOptions = optionHelper.getWebpackOptions();

const compiler = webpack(webpackOptions);

if (opts.cache) {
cachePluginFactory.addPlugin(target, compiler);
}

if (typeof opts.storeStatsTo === "string") {
grunt.config.set(opts.storeStatsTo, stats.toJson(opts.stats));
if (opts.progress) {
processPluginFactory.addPlugin(compiler, webpackOptions);
}

if (stats.hasErrors()) {
// in case opts.stats === false we still want to display errors.
grunt.log.writeln(stats.toString(opts.stats || "errors-only"));
if (opts.failOnError) {
// construct error without stacktrace, as the stack is not relevant here
const error = new Error();
error.stack = null;
done(error);
const handler = (err, stats) => {
if (err) {
done(err);
return;
}
}

keepalive = keepalive || opts.keepalive;
if (opts.stats && !stats.hasErrors()) {
grunt.log.writeln(
stats
.toString(opts.stats)
// add plugin version with and without colors
.replace(
/(\n(.*)Version: webpack (.*)\d+\.\d+\.\d+(.*))\n/,
`$1$2 / grunt-webpack $3${pkg.version}$4\n`,
),
);
}

if (--runningTargetCount === 0 && !keepalive) done();
};
if (typeof opts.storeStatsTo === "string") {
grunt.config.set(opts.storeStatsTo, stats.toJson(opts.stats));
}

if (opts.watch) {
compiler.watch(webpackOptions.watchOptions || {}, handler);
} else {
compiler.run(handler);
}
if (stats.hasErrors()) {
// in case opts.stats === false we still want to display errors.
grunt.log.writeln(stats.toString(opts.stats || "errors-only"));
if (opts.failOnError) {
// construct error without stacktrace, as the stack is not relevant here
const error = new Error();
error.stack = null;
done(error);
return;
}
}

keepalive = keepalive || opts.keepalive;

if (--runningTargetCount === 0 && !keepalive) done();
};

if (opts.watch) {
compiler.watch(webpackOptions.watchOptions || {}, handler);
} else {
compiler.run(handler);
}
});
});
},
);
Expand Down

0 comments on commit 312a1fc

Please sign in to comment.