Skip to content
This repository has been archived by the owner on Feb 18, 2024. It is now read-only.

Neutrino refactor #119

Merged
merged 7 commits into from
Mar 17, 2017
Merged

Conversation

jaridmargolin
Copy link
Contributor

@jaridmargolin jaridmargolin commented Mar 16, 2017

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.

@jaridmargolin
Copy link
Contributor Author

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?

@eliperelman
Copy link
Member

@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');
Copy link
Member

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!

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);
Copy link
Member

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.

`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)
Copy link
Member

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


process.exit(1);
});
return args.inspect
Copy link
Member

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

}

run(args._[0], [...new Set(pkgPresets.concat(args.presets))]);
run(args._[0], args)
Copy link
Member

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. :)

@@ -6,6 +6,7 @@ const Config = require('webpack-chain');
const ora = require('ora');
const merge = require('deepmerge');

const cwd = process.cwd();
Copy link
Member

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.

}

requirePreset(preset) {
const paths = [ join(cwd, preset), join(cwd, 'node_modules', preset), preset ];
Copy link
Member

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

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
Copy link
Member

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.

@eliperelman
Copy link
Member

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.

@jaridmargolin jaridmargolin force-pushed the neutrino-refactor branch 2 times, most recently from 7070398 to 08f7fd1 Compare March 16, 2017 17:21
@jaridmargolin
Copy link
Contributor Author

I will add tests for import functionality now that it is moved into the api and the functionality can more easily be run in isolation.

@jaridmargolin
Copy link
Contributor Author

Other than that, I believe I addressed all concerns mentioned in the review.

.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',
Copy link
Member

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.

middleware(this, options);
}

import (middlewares) {
Copy link
Member

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.


import (middlewares) {
middlewares = Array.isArray(middlewares) ? middlewares : [middlewares];
this.requireMiddlewares(middlewares).forEach(middleware => api.use(preset));
Copy link
Member

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

Copy link
Contributor Author

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

this.requireMiddlewares(middlewares).forEach(middleware => api.use(preset));
}

requireMiddlewares(middlewares) {
Copy link
Member

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.

return middlewares.map(middleware => this.requireMiddleware(middleware));
}

requireMiddleware(middleware) {
Copy link
Member

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 => {
    // ...
  });
}

}

requireMiddleware(middleware) {
const paths = [
Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Member

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.

@jaridmargolin jaridmargolin force-pushed the neutrino-refactor branch 3 times, most recently from 45f7b07 to ddbfc25 Compare March 17, 2017 01:26
throw exception;
}
}
const inspect = pipe(sort, partialRight(stringify, [null, 2]), console.log, process.exit);
Copy link
Member

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.

}

run(args._[0], [...new Set(pkgPresets.concat(args.presets))]);
run(args._[0], args).catch(err => {
console.error(err || `Error during ${args._[0]}`)
Copy link
Member

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

Copy link
Contributor Author

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

}

import(middleware) {
this.require(middleware).forEach(middleware => this.use(middleware))
Copy link
Member

Choose a reason for hiding this comment

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

Semicolon.

}

require(middleware) {
return requireMiddleware(middleware, this.options)
Copy link
Member

Choose a reason for hiding this comment

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

Semicolon.

return this.watcher();
})
.then(() => this.emitForAll('start', args));
return this.runCommand('start', args, () => {
Copy link
Member

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';
Copy link
Member

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);
Copy link
Member

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);
Copy link
Member

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 => {
Copy link
Member

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 => {
Copy link
Member

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

Copy link
Member

@eliperelman eliperelman left a comment

Choose a reason for hiding this comment

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

🎉

@eliperelman
Copy link
Member

Very well done.

@eliperelman eliperelman merged commit 69360d0 into neutrinojs:master Mar 17, 2017
@jaridmargolin jaridmargolin deleted the neutrino-refactor branch March 18, 2017 07:20
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants