Skip to content

Provide options.scriptExtensions for 2.x#242

Merged
bajtos merged 1 commit intostrongloop:2.xfrom
supasate:2.x
Apr 3, 2017
Merged

Provide options.scriptExtensions for 2.x#242
bajtos merged 1 commit intostrongloop:2.xfrom
supasate:2.x

Conversation

@supasate
Copy link
Copy Markdown
Contributor

Description

Provide scriptExtensions option for custom loading.
Due to require.extensions is not used by some framework (e.g. jest). So, this feature allows specifying file extensions supported by custom loaders.

Related issues

strongloop/loopback#3204

Checklist

  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style
    guide

@slnode
Copy link
Copy Markdown

slnode commented Mar 23, 2017

Can one of the admins verify this patch? To accept patch and trigger a build add comment ".ok\W+to\W+test."

@slnode
Copy link
Copy Markdown

slnode commented Mar 23, 2017

Can one of the admins verify this patch?

2 similar comments
@slnode
Copy link
Copy Markdown

slnode commented Mar 23, 2017

Can one of the admins verify this patch?

@slnode
Copy link
Copy Markdown

slnode commented Mar 23, 2017

Can one of the admins verify this patch?

@bajtos
Copy link
Copy Markdown
Member

bajtos commented Mar 24, 2017

@supasate thank you for the pull request! Does @clarkorz's #240 (comment) apply here too?

The third param onlyScriptsExportingFunction cause .js files of models will not be loaded.
Line 142 in utils.js checking typeof require.extensions['.js'] === 'function' which will be false when using jest.

@bajtos
Copy link
Copy Markdown
Member

bajtos commented Mar 24, 2017

@slnode ok to test

@bajtos bajtos self-assigned this Mar 24, 2017
@bajtos bajtos self-requested a review March 24, 2017 08:34
@bajtos bajtos added the feature label Mar 24, 2017
Copy link
Copy Markdown
Member

@bajtos bajtos left a comment

Choose a reason for hiding this comment

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

The patch looks good in general, I'd like you to clean up the code a bit - please take a look at my comments below.

Comment thread lib/compiler.js Outdated

var appRootDir = options.appRootDir = options.appRootDir || process.cwd();
var env = options.env || process.env.NODE_ENV || 'development';
var scriptExtensions = options.scriptExtensions = options.scriptExtensions ||
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Modifying input arguments (setting options.scriptExtensions) is a bad practice, we have been bitten by this in the past. Please treat options are readonly/frozen.

I see that you are accessing options.scriptExtensions at https://github.com/strongloop/loopback-boot/pull/242/files#diff-ee97c5091b89979aace94674818996baR722.

Here are two of many possible solutions how to keep options frozen and still have scriptExtensions set:

// use prototypal inheritance
options = Object.create(options);

// shallow-copy properties
options = util._extend({}, options);

(A line like the above would go to L38-39 above, before we start processing the options object.)

Comment thread lib/compiler.js Outdated
modelDefinitions, scriptExtensions) {
var registry = verifyModelDefinitions(rootDir, modelDefinitions,
scriptExtensions) ||
findModelDefinitions(rootDir, sources, scriptExtensions);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The indentation makes it difficult to distinguish function parameters from the second function call. I am proposing the following style:

  var registry = verifyModelDefinitions(rootDir, modelDefinitions,
                                        scriptExtensions);

  if (!registry) {
    registry = findModelDefinitions(rootDir, sources, scriptExtensions);
  }

Comment thread lib/compiler.js Outdated
var appRootDir = options.appRootDir = options.appRootDir || process.cwd();
var env = options.env || process.env.NODE_ENV || 'development';
var scriptExtensions = options.scriptExtensions = options.scriptExtensions ||
_.keys(require.extensions);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Considering that you are converting this array back into an object down in isPreferredExtension, I think it's better to make the conversion right here.

var scriptExtensions = options.scriptExtensions ?
  arrayToObject(scriptExtensions) : 
  require.extensions;

Comment thread lib/compiler.js Outdated

function isPreferredExtension(filename, extensions) {
var includeExtensions = extensions ?
arrayToObject(extensions) : require.extensions;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since isPreferredExtension is a private method, there is no need to keep support for the old one-arg API.

function isPreferredExtension(filename, includeExtensions) {
  assert(!!includeExtensions, '"includeExtensions" argument is required');
  // the code below remains unchanged
  var ext = path.extname(filename);
  return (ext in includeExtensions) && !(ext in getExcludedExtensions());}

process.nextTick(function() {
process.bootFlags.push('customFinished');
callback();
});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is unnecessarily complex. The thing to verify in your test is whether loopback-boot recognises a custom extension. There is no need to verify how boot script are loaded/executed again.

'use strict';

module.exports = function(app, callback) {
  process.bootFlags.push('customjs');
  callback();
};

Same comment applies to custom.customjs2.

Comment thread test/executor.test.js Outdated
});
});

it('should load scripts specified in options.scriptExtensions',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it('searches boot file extensions specified in options.scriptExtensions')

See also http://loopback.io/doc/en/contrib/style-guide.html#test-naming

@bajtos
Copy link
Copy Markdown
Member

bajtos commented Mar 24, 2017

@supasate also please take a look at #240 (comment) - please add a test reproducing the issue (I think the trick will be to use a model file with a custom extension that's not registered in required.extensions.)

@bajtos
Copy link
Copy Markdown
Member

bajtos commented Mar 24, 2017

Also please rebase your patch on top of the latest 2.x branch, I fixed the failing Node 0.10 builds in #244.

@supasate
Copy link
Copy Markdown
Contributor Author

supasate commented Mar 30, 2017

Hi @bajtos, I've rebased and fixed issues based on your comments. I made a sample repo at https://github.com/supasate/loopback-boot-with-jest. I'll squash later if it's alright. I also fixed the 3.x branch.

@supasate
Copy link
Copy Markdown
Contributor Author

I also found another issue about file name resolving (strongloop/loopback#2201) (not related to this PR but it will make jest failed to run).

Comment thread lib/compiler.js Outdated
}

function resolveAppScriptPath(rootDir, relativePath, resolveOptions) {
function resolveAppScriptPath(rootDir, relativePath, resolveOptions,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please keep resolveOptions as the last function arg. I would consider converting scriptExtensions into a property in resolveOptions, to keep the number of function arguments reasonably short.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

After refactoring, there is no need to use scriptExtensions in this function anymore.

Comment thread lib/compiler.js
scriptExtensions, normalization,
modelInstructions) {
// load mixins from `options.mixins`
var sourceFiles = options.mixins || [];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is the rationale for this change? I mean why to pass options properties as individual arguments? buildAllMixinInstructions is already accepting way too many arguments, we should look into ways how to make that list shorter, not longer...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I tried to make code look consistent with how mixinDirs and mixinSources are declared and passed to the function..

Do you want to refactor those arguments by passing only options and set their default values in buildAllMixinInstructions instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I refactored by passing only options.

Comment thread lib/compiler.js Outdated
(typeof require.extensions[otherFileExtension]) === 'function') {
results.push(otherFile);
}
// if (!onlyScriptsExportingFunction)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We don't keep commented-out code around, please remove.

If I understand this code correctly, the intention was:

  • return a file with any extension when onlyScriptsExportingFunction is not set
  • return a file with an extension that can be loaded by Node.js runtime (an extension that is registered in require.extensions) when onlyScriptsExportingFunction is set

We should preserve these two modes.

I am proposing to replace onlyScriptsExportingFunction arg with scriptExtensions and modify all function callers accordingly.

// implementation here
if (!scriptExtensions || otherFileExtension in scriptExtensions)
  results.push(otherFile);

// usage - flag not set
fixFileExtension(filepath, files)

// usage -flag set
fixFileExtension(filepath, files, scriptExtensions)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oops! That's my mistake. I tested something and forgot to uncomment. I'll fix as you suggested.

@bajtos
Copy link
Copy Markdown
Member

bajtos commented Mar 31, 2017

@supasate thank you for the pull request, please take a look at the comment I left above. Also please rebase your patch on top of the latest 2.x branch.

@supasate
Copy link
Copy Markdown
Contributor Author

supasate commented Mar 31, 2017

About rebasing on top of the latest 2.x branch, do you mean latest of 2.x branch or 2.x-latest branch? (I've rebased with 2.x branch because it seems its last commit is more up-to-date than 2.x-latest.) A little bit confuse with the branch names.

Copy link
Copy Markdown
Member

@bajtos bajtos left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@bajtos bajtos merged commit 440a75d into strongloop:2.x Apr 3, 2017
@bajtos
Copy link
Copy Markdown
Member

bajtos commented Apr 3, 2017

Landed, thank you for the contribution!

@bajtos
Copy link
Copy Markdown
Member

bajtos commented Apr 3, 2017

Released in loopback-boot@2.24.0 🎉

@supasate
Copy link
Copy Markdown
Contributor Author

supasate commented Apr 3, 2017

Thanks @bajtos !

I think you can review the 3.x branch also. The code is almost the same except that it is splitted into several files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants