Plugins & Loaders

One of the great advantages of using Spike is that, since it uses Webpack as its core compiler, there is already a great eocsystem of webpack loaders and plugins available for use.

Using Loaders With Spike

You can add a loader to spike in exactly the same way as you'd add it to a normal webpack config file:

module.exports = {
  // ...
  module: {
    rules: [
      { test: /\.foo$/, use: [{ loader: 'foo-loader' }] }
    ]
  }
}

In this example, we have a hypothetical example of a "foo loader" which does some sort of processing on files with a .foo extension. In this case, the foo loader would do it's processing, then pass the results to spike's internal static asset pipeline, which would write the file's contents to the correct location.

There are a few things to note about this process. First, Spike does not require you to require a file from somewhere in order for it to be processed and written, and this holds for any custom loaders. Also note that if the loader "emits" the file, this will be suppressed, instead spike will handle the file writing and output.

Note that by default, Spike's core loaders will pull in any files with .html, .css, and .js extensions for processing. If you add a loader that uses one of these extensions, your custom loader will overwrite spike's core loaders for that file. So if you want to use a loader on a .js file, then have it processed with babel as spike normally does, you need to add the babel-loader to the loader chain yourself like this:

module.exports = {
  // ...
  module: {
    rules: [{
      test: /\.foo$/,
      use: [
        { loader: 'babel-loader' },
        { loader: 'foo-loader' }
      ]
    }]
  }
}

If you want to exactly emulate spike's internal loader config, check the source.

If you wish to include a loader in the pure webpack manner without any influence from spike whatsoever, this is possible using the _skipSpikeProcessing flag. You can add this flag to the options of any single loader within a rule and it will take effect. In this case the file must be require'd in order to be included in the bundle, and if the file needs to be written somewhere, the loader handles the emission using emitFile. This is best suited for files which are simply required into client-side javascript and do not need to be written to the output folder. For example:

module.exports = {
  // ...
  module: {
    rules: [{
      test: /\.foo$/,
      use: [
        {
          loader: 'foo-loader',
          options: { _skipSpikeProcessing: true }
        }
      ]
    }]
  }
}

Note that if you use the _skipSpikeProcessing flag on a .js, .css, and/or .html file, it will entirely skip spike's internal loaders, and you will need to process and write these files entirely on your own. This is not recommended.

Finally, if you'd like to have spike process a file for you and also change the extension when it is emitted, you can do this by adding an _spikeExtension property to the options of any of your loaders. For example:

module.exports = {
  // ...
  module: {
    rules: [{
      test: /\.foo$/,
      use: [{
        loader: 'foo-loader',
        options: { _spikeExtension: 'txt' }
       }]
    }]
  }
}

This would load up test.foo, transform it however the foo-loader decides to, and emit it as test.txt. Any extension works here -- do not include a dot, just the extension name. And spike will remove the extra property from the loader's options before the file is processed for both _spikeExtension and _skipSpikeProcessing, so don't worry about polluting your options objects 😁

🚧

Custom Loader Warning!

If you are using your own custom loader, you will need for it to output a javascript-compatible format so that webpack understands it. If you do not do this, you will get a "ModuleBuildError" which tells you that you need a loader to handle this type of file.

If you are using a loader that outputs plaintext and intend it to work with spike, a quick fix is to use the source-loader in order to transform the plaintext into a format that webpack can understand and write to a file. Just prepend your loader config with source-loader to do this.

For example, { use: [{ loader: 'source-loader }, { loader: 'foo-loader' }]

Finally, note that if you are using spike as a global module and using a locally-installed loader, you might have issues resolving the loader correctly. If you do, you can use webpack's resolveLoader config option to ensure that it is able to resolve your loader from wherever you are trying to load it.

Using Plugins With Spike

Using plugins with spike is very straightforward. You can add them directly through a plugins option in the config, as such:

module.exports = {
  // ...
  plugins: [somePlugin()]
}

By default, plugins will run before spike's primary file processing plugin. Some plugins, however, work better if they run after spike's file processing. For example, any plugins that take stock of the final assets that will be emitted, such as plugins that generate sitemaps or service workers. For this case, you'll want to use afterSpikePlugins instead, as such:

module.exports = {
  // ...
  afterSpikePlugins: [somePlugin()]
}

The only things to note with plugins are that you can take advantage of some extra spike-specific configuration values. First, all of the assets that pass through spike core other than javascript are processed by source-loader, which makes the raw buffered source of each file available to plugins. Check out source-loader's readme for more info on how to do this.

Spike also creatively extends the webpack base configuration object in order to hold on to any spike-specific configuration properties, like matchers, dumpDirs, the environment, etc. You can grab these easily using Spike Util, specifically the util.getSpikeOptions() method. You are welcome to use any of these properties in your plugin, but make sure to note that it will only work with spike if you do!

Check out some of the plugins that already exist here. And if you are authoring a new plugin, make sure to use the spikeplugin keyword so that it shows up in the directory!