Wrapping NPM packages
If you are familiar with NPM just will know that many packages have some entry point script which can be used as a tool on the command-line. You are probably quite familiar with Gulp too and if you already used the Gulp functionality that comes with the Gulp plugin, you might be pondering doing the same approach for your favourite Node tool package.
The process if doing this is relatively straight-forward. It involves creating an extension and a resolver. Once you have these you can continue implementing your own task types.
The following example are shown in Groovy, but you can use Java or Kotlin for your implementation if you wish. |
This approach can be used in a gradle script, buildSrc
or a standalone plugin.
Creating the extension
Assume for the moment that there is a packaged tool called FooBar which you want to wrap.
Firstly, create an extension class that extends AbstractPackageWrappingExtension.
There are three methods in protected scope that you will need to implement in addition to two constructors.
@CompileStatic
class FooBarExtension extends AbstractPackageWrappingExtension {
FooBarExtension(Project project) { (1)
super(project)
}
FooBarExtension(Task task) { (2)
super(task,'foobar') (3)
}
@Override
protected String getExtensionName() { (4)
'name-of-this-extension'
}
@Override
protected String getEntryPoint() { (5)
'bin/foobar.js'
}
}
1 | Attaches the extension to a project. |
2 | Attaches the extension to a task |
3 | It is necessary top pass the name of the project extension, when creating the task extension. |
4 | The name that this extension will be known by. This is used by the superclass to resolve project extensions when the extensions are attached to a task. (This functionality might be removed in the future). |
5 | This is the entrypoint script file and must be specified relative to the package folder after installation. |
You can now add methods to your extension as if necessary to reflect the functionality of the wrapped package.
For instance the GulpExtension class adds gulpFile
and requires
to deal with the --gulpfile
and --requires
parameters of gulp.js
.
If you add methods where the returned values can be overridden in a task in a similar fashion to what can be done in NpmTask
and GulpTask
, you will need to add some logic to handle those cases.
Here is an example of how it is being done in GulpExtension
to retrieve the value for gulpFile
.
GulpExtension(GulpTask task) {
this.requires = task.project.objects.setProperty(String) (1)
this.requiresProvider = this.requires.orElse(((GulpExtension) projectExtension).requiresProvider) (2)
}
1 | All requires recorded as a SetProperty . |
2 | If this is attached to a task, check if the value is set. Use it if it is, otherwise defer to the project extension. If this is a project extension use the local value. |
Create a task type
In some cases you might want to also add a task type, similar to GulpTask
.
One approach is to extend AbstractNodeBaseTask.
@CompileStatic
class FooTask extends AbstractNodeBaseTask {
FooTask() {
super()
fooExtension = (FooExtension)(extensions.create('foo',FooExtension,this))
}
@TaskAction
void exec() {
NodeJSExecSpec execSpec = createExecSpec() (1)
execSpec.script fooExtension.resolvedExecutable.executable.absolutePath (2)
/* Configure execution specification against any properties */
runExecSpec(execSpec) (3)
}
private FooExtension fooExtension
}
1 | Base class has ability to create and pre-configure an execution specification. |
2 | Resolve the script from the resolver. |
3 | Execute the execution specification. |