Skip to content

Protobuf JS Plugin

Dmytro Dashenkov edited this page Aug 12, 2020 · 7 revisions

This guide explains the usage of the Spine Protobuf JS Gradle plugin.

About the plugin

The Protobuf JS Plugin performs enhancement of JavaScript files generated by Protobuf compiler.

In general, all the enhancements are performed for the features needed by Spine Web. The framework users should not interact with the enhanced API. However, those enhancements are required for the Spine Web library to function properly.

The plugin can be applied as follows:

apply plugin: 'io.spine.tools.proto-js-plugin'

The basic configuration

The plugin configuration is performed via the protoJs extension and also requires configuring of the Protobuf compiler.

The minimal working configuration is following:

/**
 * Compiles Protobuf sources into JavaScript.
 *
 * <p>This is a lifecycle task. It performs no action but triggers all the tasks which perform
 * the compilation.
 */
task compileProtoToJs {
}

protobuf {
    generatedFilesBaseDir = "${projectDir}/generated"
    protoc {...}
    generateProtoTasks {
        all().each { final task ->
            task.builtins {
                js {
                    // Currently, the Proto JS plugin supports only Common JS imports.
                    option "import_style=commonjs"
                }
                task.generateDescriptorSet = true
                task.descriptorSetOptions.path = "${projectDir}/build/descriptors/${task.sourceSet.name}/known_types.desc"
            }
            compileProtoToJs.dependsOn task
        }
    }
}

protoJs {
    generateParsersTask().dependsOn compileProtoToJs
    // There is no need to configure paths to generated Protobufs
    // since the compiler uses default paths.
}

Custom paths to generated files

You can configure the paths where to put generated Protobuf files and the descriptor set file as follows:

// The example omits configuration which is not related to paths.

ext {
    genProtoBaseDir = projectDir
    genProtoSubDir = "proto"
    genProtoMain = "$genProtoBaseDir/main/$genProtoSubDir"
}

protobuf {
    generatedFilesBaseDir = genProtoBaseDir
    generateProtoTasks {
        all().each { final task ->
            task.builtins {
                js {
                    outputSubDir = genProtoSubDir
                    option "import_style=commonjs"
                }
                task.generateDescriptorSet = true
                task.descriptorSetOptions.path = "${projectDir}/custom/${task.sourceSet.name}/known_types.desc"
            }
        }
    }
}

protoJs {
    mainGenProtoDir = genProtoMain
    mainDescriptorSetPath = "$projectDir/custom/main/known_types.desc"
    // The same for test files.
}

Reusing NPM modules with generated Protobufs

Currently, Protobuf compiler generates relative imports for dependencies of a file.

So, for a .proto file with the following snippet:

package spine.base;
import "spine/options.proto";

The compiler generates:

var spine_options_pb = require('../../spine/options_pb.js');

In this case, you need to generate the options file by itself. Though, the plugin makes it possible to adjust import paths.

Default adjustment of imports

By default, the plugin handles imports of some Protobufs provided by Spine. See Extension.predefinedModules() for the details.

So the import above is adjusted to:

var spine_options_pb = require('spine-web/proto/spine/options_pb.js');

The spine-web is an artifact published to NPM, so your project needs to have it as a dependency.

Custom adjustment of imports

You can also specify custom modules and directories they provide. So the plugin will replace relative imports by imports of module files.

An example:

protoJs {
      modules = [
           // The module provides `company/client` directory (not including subdirectories).
           // So, an import path like {@code ../company/client/file.js}
           // becomes {@code client/company/client/file.js}.
           'client' : ['company/client'],

           // The module provides `company/server` directory (including subdirectories).
           // So, an import path like {@code ../company/server/nested/file.js}
           // becomes {@code server/company/server/nested/file.js}.
           'server' : ['company/server/*'],

           // The module provides 'proto/company` directory.
           // So, an import pah like {@code ../company/file.js}
           // becomes {@code common-types/proto/company/file.js}.
           'common-types' : ['proto/company']
     ]
}

In the configuration above, the modules property is a map. A key is a name of an NPM module and a value is a list of patterns describing directories provided by the module. The patterns are used to match and resolve imports.