-
Notifications
You must be signed in to change notification settings - Fork 213
Neutrino refactor #119
Neutrino refactor #119
Conversation
Additionally, I didn't see tests built around the CLI functionality. Any insight on how you are currently working with this code and maintaining confidence in the changes? |
@jaridmargolin I typically don't test CLI-specific functionality as it usually involves spawning a process and reading its output. I'll typically stick with testing APIs. If someone wanted to add a PR for a way to test the CLI that was simple enough, I would accept it. |
@@ -10,17 +10,8 @@ const { pathOr, pipe, partialRight } = require('ramda'); | |||
const { join } = require('path'); | |||
const stringify = require('javascript-stringify'); | |||
const sort = require('deep-sort-object'); | |||
const optional = require('optional'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oooo, never seen optional
before, very cool!
packages/neutrino/bin/neutrino
Outdated
const options = pathOr({}, ['neutrino', 'options'], pkg); | ||
const config = pathOr({}, ['neutrino', 'config'], pkg); | ||
const api = new Neutrino(Object.assign(options, { config })); | ||
const inspect = pipe(sort, partialRight(stringify, [null, 2]), console.log, process.exit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move inspect
above api
so the flow to Neutrino is clear.
packages/neutrino/bin/neutrino
Outdated
`Ensure this module can be found relative to the root of the project.`); | ||
}); | ||
// Grab all presets and merge them into a single webpack-chain config instance | ||
api.requirePresets(presets) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's either one-line this or put each chain on its own line.
api
.requirePresets(presets)
.forEach(preset => api.use(preset));
packages/neutrino/bin/neutrino
Outdated
|
||
process.exit(1); | ||
}); | ||
return args.inspect |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put ternary operators on the preceding line:
return args.inspect ?
inspect(api.getWebpackOptions()) :
api[command](args);
packages/neutrino/bin/neutrino
Outdated
} | ||
|
||
run(args._[0], [...new Set(pkgPresets.concat(args.presets))]); | ||
run(args._[0], args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's only a single method call here, so we can probably put this one on the same line:
run(args._[0], args).catch(err => {
console.error(err || `Error during ${args._[0]}`);
process.exit(1);
});
I like the updated error messaging. :)
packages/neutrino/src/neutrino.js
Outdated
@@ -6,6 +6,7 @@ const Config = require('webpack-chain'); | |||
const ora = require('ora'); | |||
const merge = require('deepmerge'); | |||
|
|||
const cwd = process.cwd(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to not use process.cwd()
here, if possible, outside of the constructor. See line 16, which introduces options.root
.
packages/neutrino/src/neutrino.js
Outdated
} | ||
|
||
requirePreset(preset) { | ||
const paths = [ join(cwd, preset), join(cwd, 'node_modules', preset), preset ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we use options.root
, which is usually process.cwd()
, we shouldn't need cwd
. Remove inner spaces from the array brackets. Paths is intermediary, so we can remove the variable:
const module = [
join(this.options.root, preset),
join(this.options.root, 'node_modules', preset),
preset
].find(path => this.requirePath(path));
packages/neutrino/bin/neutrino
Outdated
throw new Error(`Neutrino cannot find a module with the name or path '${preset}'. ` + | ||
`Ensure this module can be found relative to the root of the project.`); | ||
}); | ||
// Grab all presets and merge them into a single webpack-chain config instance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just had a thought. We are using two different nomenclatures for items we are requiring: "presets" and "middleware". Presets are a carryover from pre-v5, and presets are technically middleware. Instead of naming things by what we are loading, what if we named them by the action we are going to take on them?
use
We already do this in the API. We use presets. We use middleware. The CLI interface should be synonymous with the API action.
So I'm proposing to change the CLI flag from --presets
to --use
. Would you make that change along with this? Then the package.json object would also change from neutrino.presets : []
to neutrino.use : []
.
That would inform the name of this methods better too:
api
.requireMiddleware(middleware)
.forEach(middleware => api.use(middleware))
Or maybe requireMiddleware
is too verbose, and the only thing we ever require is middleware, so it can be simply api.require
or api.import
. One thing I like about the connotation of import
is that it presupposes use
:
middleware.forEach(middleware => api.import(middleware)) // Does a require AND a use
TLDR; Let's change the CLI to use --use
, change neutrino.presets
to neutrino.use
for package.json, use the variable of middleware
instead of presets
, and create Neutrino#import
which does a require
and a use
.
Really happy with the changes this PR brings, and I've given some comments about how we can improve the API and its clarity with the CLI even more. |
7070398
to
08f7fd1
Compare
I will add tests for |
Other than that, I believe I addressed all concerns mentioned in the review. |
packages/neutrino/bin/neutrino
Outdated
.option('presets', { | ||
description: 'A list of Neutrino presets used to configure the build', | ||
.option('use', { | ||
description: 'A list of Neutrino middlewares used to configure the build', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it sounds silly, but "middleware" is plural and singular. Let's use the word "middleware" over "middlewares" everywhere.
packages/neutrino/src/neutrino.js
Outdated
middleware(this, options); | ||
} | ||
|
||
import (middlewares) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove extra space after word import
.
packages/neutrino/src/neutrino.js
Outdated
|
||
import (middlewares) { | ||
middlewares = Array.isArray(middlewares) ? middlewares : [middlewares]; | ||
this.requireMiddlewares(middlewares).forEach(middleware => api.use(preset)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can probably inline the middleware (also still a reference to preset
here):
this
.requireMiddleware(Array.isArray(middleware) ? middleware : [middleware])
.forEach(middleware => api.use(middleware));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is why I need to get some tests in place :)
packages/neutrino/src/neutrino.js
Outdated
this.requireMiddlewares(middlewares).forEach(middleware => api.use(preset)); | ||
} | ||
|
||
requireMiddlewares(middlewares) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove this method, if we do the mapping in requireMiddleware
, and normalize the array. See my comment there.
packages/neutrino/src/neutrino.js
Outdated
return middlewares.map(middleware => this.requireMiddleware(middleware)); | ||
} | ||
|
||
requireMiddleware(middleware) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can make this handle singular or plural middleware, just like import
:
requireMiddleware(middleware) {
if (!Array.isArray(middleware)) {
middleware = [middleware];
}
return middleware.map(middleware => {
// ...
});
}
packages/neutrino/src/neutrino.js
Outdated
} | ||
|
||
requireMiddleware(middleware) { | ||
const paths = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be named module
instead of paths
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is why I need to get some tests in place :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually curious how this wasn't picked up by linting in travis.
45f7b07
to
ddbfc25
Compare
packages/neutrino/bin/neutrino
Outdated
throw exception; | ||
} | ||
} | ||
const inspect = pipe(sort, partialRight(stringify, [null, 2]), console.log, process.exit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function isn't dependent on anything inside of run
, so let's pull this one into the outer scope.
packages/neutrino/bin/neutrino
Outdated
} | ||
|
||
run(args._[0], [...new Set(pkgPresets.concat(args.presets))]); | ||
run(args._[0], args).catch(err => { | ||
console.error(err || `Error during ${args._[0]}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing semicolon. (I fixed the linter in another patch, these should be picked up on your next commit.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhh good. I'll rebase your changes (I've been using standard lately, and sometimes its difficult to switch between projects which don't use it).
packages/neutrino/src/neutrino.js
Outdated
} | ||
|
||
import(middleware) { | ||
this.require(middleware).forEach(middleware => this.use(middleware)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Semicolon.
packages/neutrino/src/neutrino.js
Outdated
} | ||
|
||
require(middleware) { | ||
return requireMiddleware(middleware, this.options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Semicolon.
packages/neutrino/src/neutrino.js
Outdated
return this.watcher(); | ||
}) | ||
.then(() => this.emitForAll('start', args)); | ||
return this.runCommand('start', args, () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be:
return this.runCommand('start', args, () => (this.getWebpackOptions.devServer ? this.devServer() : this.watcher()));
@@ -0,0 +1,45 @@ | |||
import { join } from 'path'; | |||
import { outputFile, remove } from 'fs-extra'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we can use outputFile
and remove
below, let's make this:
import { outputFile as fsOutputFile, remove as fsRemove } from 'fs-extra';
import requireMiddleware from '../src/requireMiddleware'; | ||
|
||
const cwd = process.cwd() | ||
const poutputFile = pify(outputFile); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to outputFile
.
|
||
const cwd = process.cwd() | ||
const poutputFile = pify(outputFile); | ||
const premove = pify(remove); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to remove
.
const moduleMiddlewarePath = join(modulePath, 'index.js'); | ||
|
||
|
||
test.before(t => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Async functions:
test.before(async (t) => {
await Promise.all([
outputFile(rootMiddlewarePath, `module.exports = 'root';`),
outputFile(errorMiddlewarePath, '[;'),
outputFile(moduleMiddlewarePath, `module.exports = 'mymodule';`)
]);
process.chdir(rootPath);
});
]).then(() => process.chdir(rootPath)); | ||
}); | ||
|
||
test.after.always(t => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test.after.always(async (t) => {
await remove(rootPath);
process.chdir(cwd);
};
… --env option to CLI).
ddbfc25
to
19ce099
Compare
19ce099
to
7f518f8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
Very well done. |
Tried to break these up into nice little reviewable commit chunks.
1. Move require preset functionality to api.
The idea here is to expose the requirePresets functionality for reuse by anyone wishing to consume the api. I don't see any specific reason why it would be specific to the CLI. Any oversight?
2. Make run method in bin/neutrino self contained.
This one may be stylistic, but I didn't understand why some variables were declared in the method, and some were declared at the top of the file. This moves all of the variables to where they are actually utilized. Additionally, I believe this has the added benefit of making
run
more portable if in the future there is reason to have it usable outside of the CLI.3. Move run process logic to location of call.
The moves the logging and process management to the location in which
run
is called. I believe this is more appropriate place and makes the code easier to reason about... The idea is, "this is what we do when run fails." Additionally this closes, #118.* Hopefully not out of line making structural changes to the code. I have very much enjoyed working with the neutrino community, and thought I could give back a little.