@toolbarthomas/harbor

0.800.0 • Public • Published

Harbor

Harbor is an asset builder that fits within the theme architecture of Drupal 8+ setups. It can create Drupal compatible themes without the need to install the actual CMS. With the help of Storybook it can generate the Twig templates that are used by Drupal themes.

The assets are processed on a very basic level, stylesheets can be compiled with the included compiler and Babel will transform the defined javascript files to enable JS compatibility for older browsers.

It is optional to use the bundled workers but this ensures that everything works correctly within Drupal and your styleguide environment. It is also possible to include other frameworks within the environment by simply adding it within a compatible theme library configuration.

Get more information for implementing Harbor in your current Drupal environment.

Setup

You can install Harbor via NPM (Nodejs is required in order to do this.):

$ npm install @toolbarthomas/harbor

Then you can start Harbor by simply running:

$ node node_modules/@toolbarthomas/harbor/index.js

Harbor will run the default tasks when there are no CLI arguments defined for the initial command. The following CLI arguments can be used in order to customize the build process.

Argument Description
--task Starts one or more worker tasks to compile the theme assets.
--verbose Writes extended console messages within the command line.
--styleguide Starts the styleguide builder.
--watch Observes for file changes for the initiated tasks.
--minify Minifies the processed assets.
--test Defines the testing phase for Backstopjs, should be test, reference or approve.

You can also use the harbor command instead if you installed it globally: This will only run the default workers but you can use additional parameters like the local commands. Keep in mind that you need to be in the correct working directory in order to run it correctly.

$ npm install -g @toolbarthomas/harbor

Installing Harbor globally will lock you in a specific version so keep in mind it can break your workflow if you installed the newest version without fixing the breaking-changes.

$ harbor
# or
$ harbor --styleguide --watch

Workers

A worker provides the core tasks for Harbor and can be adjusted within the configuration. Workers can be initiated during a Harbor process by calling the defined hook within the command or as CLI argument.

# Starts the workers that have the stylesheets hook from the configuration.
$ node node_modules/@toolbarthomas/harbor/index.js --task=stylesheets
$ node node_modules/@toolbarthomas/harbor/index.js stylesheets

This example will start the workers that are defined with the stylesheets hook, by default it will process the configured sass entry files (or it will try to use the default configured entries).

The actual hooks are defined within the default configuration of each worker, these hooks can be adjusted within your custom configuration. Workers that share the same hook will be called in parallel order by default. The order of this queue can be adjusted by adding the optional flag :: with the index value to mark the order, this will also run the queue in a sequence. This configuration example will start the Cleaner worker before the FileSync worker during the usage of the prepare task within the CLI.

...
Cleaner: {
  hook: ['clean', 'prepare::0'],
  ...
},
FileSync: {
  hook: ['sync', 'prepare::1']
  ...
}
...

Workers that share the same hook without the double colons will run in a parallel order:

# Should initiate the compile workers in a parallel order:
$ node node_modules/@toolbarthomas/harbor/index.js --task=compile

The following workers are configured within the default configuration:

Worker Description Hook(s)
AssetExporter Wraps the defined entries as a template literal module. AssetExporter export
Cleaner Cleans the defined THEME_DIST environment directory. Cleaner clean prepare default
FileSync Synchronizes the defined entry files to the THEME_DIST environment directory. FilSync sync prepare default
JsCompiler Transforms the defined entry javascript files with Babel. JSCompiler js javascripts compile default
Resolver Resolves NPM installed vendor pacakges to the THEME_DIST environment directory. Resolver resolve prepare default
SassCompiler Compiles the defined entry Sass files with Node Sass. SassCompiler sass stylesheets compile default
StyleguideHelper Creates initial storybook entries from the defined Twig templates. StyleguideHelper setup
StyleguideTester Initiates Snapshot tests for the styleguide with BackstopJS. StyleguideTester test
SVSpriteCompiler Creates one or more inline SVG sprites based from the configured entries. SVGSpriteCompiler svg images compile default

Plugins

Plugins are used to run post-process tasks like starting the storybook development server, optimizing the assets or include a file watcher. These can be defined by adding the given CLI argument hooks within your command:

$ node node_modules/@toolbarthomas/harbor/index.js --task=javascripts --minify

More plugins can be included within a single command, the following plugins are available within the default configuration, the result of certain plugins can vary between environments:

Plugin Environment Description Hook(s)
JSOptimizer production only Minifies the defined js entries within the THEME_DIST directory minify
StyleOptimizer production only Minifies the defined css entries within the THEME_DIST directory minify
StyleguideCompiler production Creates a static storybook styleguide. storybook, styleguide
StyleguideCompiler development Starts the storybook development server. storybook, styleguide
Watcher development only Watches the configured instance entries and runs the assigned workers during a file change. watch

This will only generate the actual assets that should be compatible for the Drupal environment. Keep in mind that this command will only run the configured Harbor workers, the actual development tools can be included with extra CLI arguments:

$ node node_modules/@toolbarthomas/harbor/index.js --task=javascripts --minify --watch

Environment

An optional Harbor environment can be defined by creating a dotenv file within the root of your theme directory. The following configuration can be adjusted, the default values will be used for any missing environment variable.

Environment variable Default value Description
THEME_SRC ./src Defines the working source directory for all Worker entries.
THEME_DIST ./dist Defines the build directory for all Worker entries & the styleguide development server.
THEME_PORT 8080 Defines the server port for the styleguide development server.
THEME_DEBUG false Includes sourcemaps if the defined entries support it.
THEME_ENVIRONMENT production Enables environment specific Plugins to be used.
THEME_STATIC_DIRECTORY storybook-static Defines the destination path for the static styleguide build. This is used to create multiple static builds within the codebase.
THEME_WEBSOCKET_PORT 35729 Enables attached library stylesheets to be automatically refreshed within the styleguide, the websocket won't be created if there is no port number defined.
THEME_TEST_PHASE test Defines the testing method for BackstopJS: test, reference or approve.
THEME_AS_CLI false Launches Storybook in CLI mode that is used by the StyleguideTester.

Default Configuration

The Harbor workers can be configured to point out the location of your assets. A default configuration has been defined within Harbor, a custom configuration can be used by creating harbor.config.js within the working directory.

For example:

  // harbor.config.js

  ...
  workers: {
    SassCompiler: {
      options: ...,
      hook: 'stylesheets',
      plugins: ...,
      entry: {
        main: [...]
      },
    },
  }
  ...

Common Configuration

The following configuration options are available for the default Workers & Plugins. Most options are used before the defined Worker/Plugin is actually running; like resolving the actual entry files or ignoring some source paths for the Worker/Plugin entry:

Option type Description
entry String/String[] The actual sources that will be processed for the defined Worker or Plugin.
hook String Defines the commands that should start the given Worker or Plugin
ignore String/String[] Excludes the defined path(s) from the entry sources for the defined Worker or Plugin.
options Object Defines the optional configuration for the defined Worker/Plugin, the actual options are not common since they are defined for the used NPM libraries.

Default Worker Configuration

Cleaner configuration

The Cleaner is a default Harbor worker that will delete all files within the defined environment destination directory: THEME_DIST No specific configuration is available for this Worker.

FileSync configuration

The FileSync will synchronize the defined static entries to the configured environment destination directory.

Option type Description
patterns String[] Copies the given patterns and it's folder structure to the environment destination directory.

JsCompiler configuration

The JsCompiler transforms & lints the defined entries with Babel & Eslint. The result will be written relative to the configured environment destination directory.

Option type Description
plugins Object Optional plugins that will be assigned to the Babel & Eslint instances.
plugins.eslint Object The optional Eslint plugin(configuration).
plugins.transform Object The optional Babel transform(configuration).

SassCompiler configuration

The SassCompiler renders & prepares the defined entries with Node Sass & Postcss. The result will be written relative to the configured environment destination directory.

Option type Description
options Object Optional configuration for the Node Sass compiler.
options.useLegacyCompiler Boolean Flag that enables the Node Sass compiler instead of the Dart Sass compiler.
plugins Object Optional plugins that will be assigned to the Postcss plugin.
plugins.postcss Object The optional Postcss plugin(configuration).

StyleguideHelper configuration

The StyleguideHelper creates initial Styleguide entry templates from the existing Twig templates any json or yaml file that is relative to the Twig template will be included within the styleguide entyr.

Option type Description
options Object Optional configuration for the worker.
options.configurationExtensions String[] Includes the first configuration entry from the defined extensions, the configuration is found relative within the template context.
options.defaultModuleName String Defines the name for the default entry story.
options.destinationDirectory String/null Writes the new entries to the defined directory or write it relative to the template by disabling this option.
options.disableAlias Boolean Don't use the included @theme alias and use a relative path instead.
options.extname String Use the defined extension when the styleguide entry is written to the FileSystem.
options.filterKeywords String[] Removes the defined keywords from the generated Module export, File paths won't be adjusted from this settings.
options.ignoreInitial Boolean Overwrites the existing entry files when enabled.
options.prettier Boolean Should implement your project prettier configuration to ensure the styleguide entries are written in the correct syntax.
options.sep String Defines the structure separator for the entry title.
options.structuredTitle Boolean Includes the base directory structure for the styleguide entries when enabled.
options.variants Object Includes optional module variants for the entry template.
options.variants[].context String Should match with the file that is used for the variant configuration.
options.variants[].query String Executes a regular expression match within the defined context path, scripting files are ignored.
options.variants[].transform Function Optional handler that will the matched query values.

Define StyleguideHelper variants

You can define additional module variants for each entry template by defining additional properties that will be used within the template scope:

StyleguideHelper: {
  variants: {
    modifier_class: {
      query: /[^&(){}`a-zA-Z][.][a-zA-Z-]+--[a-zA-Z-]+/g,
      context: 'scss',
      transform: (v) => v.split('.').join(''),
    },
  },
}

The query option should match a regular expression that will match everything within the given source file. This source file is based defined from the initial source file where the from option is used as file extension replacement:

src/example.twig => src/example.scss

A transform handler can be included in order to strip any unwanted character from the matched results within the regular expression.

You can also directly import the variant configuration if the defined context matches a .js, .json, .mjs or .yaml file. This will assign the extra properties to the defined variant:

// Will create a variant as module import:
// import ButtonExampleConfiguration from button.example.json
// within the template context: button.twig.
StyleguideHelper: {
  variants: {
    modifier_class: {
      context: '.example.json',
    },
  },
}

It is also possible to search for configuration files outside the directory by including the includeDirectories option within the variant configuration.

Keep in mind that the directories are resolved from the defined THEME_SRC environment path. It will use the configuration if the file config/{module}.example.json exists:

// Will create a variant as module import:
// import ButtonExampleConfiguration from button.example.json
// outside the template context: button.twig.
StyleguideHelper: {
  variants: {
    modifier_class: {
      context: '.example.json',
      includeDirectories: ['config'],
    },
  },
}

StyleguideTester configuration

The StyleguideTester enables snapshot testing of the generated styleguide. All valid stories will be extracted by Storybook and are tested with BackstopJS.

Option type Description
options Object Optional configuration for the worker & BackstopJS.
options.backstopJS Object Defines the base configuration for BackstopJS. More info
options.outputPath String The destination for the Styleguide manifest that is used for the Snapshot tester.
options.scenarioDirectory String Defines the destination directory for additional scenarios defined as YAML or JSON file.
options.staticDirectory String Defines the destination directory for the static styleguide build, to prevent removal of already generated packages.

SvgSpriteCompiler configuration

The SvgSpriteCompiler will compile the defined entries into inline SVG sprites. The result will be written relative to the configured environment destination directory.

Option type Description
prefix String The ID prefix for each icon within the compiled sprite.

Resolver configuration

The Resolver will resolve the defined packages from the node_modules to the environment destination.

Option type Description
options.cwd String The destination directory where the resolved entries will be Written into.

Default Plugin Configuration

AssetExporter configuration

The AssetExporter wraps the defined entry templates as a valid module export template literal. It is possible to include an optional literal function within the actual asset by defining a new includeLiteral Object within the options.

Option type Description
options Object Optional configuration for the AssetExporter.
options.includeLiteral Object Assigns Babel module-resolver aliases to the Storybook instance.
options.includeLiteral[].entry String Should match with the defined entry name, a custom literal will be included when there is a match.
options.includeLiteral[].export String The actual literal that can be prefixed with.
options.includeLiteral[].import String The actual import source for the optional module literal.

StyleguideCompiler configuration

The Styleguide Compiler will generate a new Storybook instance for the defined THEME_ENVIRONMENT value.

A Storybook development version can be launched by defining THEME_ENVIRONMENT='development' within your environment configuration.

This will launch a new Storybook development server with the Storybook CLI. More information about the usage of this server can be found on Storybook

You can also create a static version of your Storybook instance by setting THEME_ENVIRONMENT to production. This static styleguide will be written to the defined THEME_DEST destination and will resolve the processed assets within the static package. Keep in mind that some assets cannot be displayed when viewing the static HTML document directly for security reasons.

This can be resolved by viewing the actual result from a (local) webserver.

Option type Description
options Object Optional configuration for the Storybook compiler.
options.addons Array Should contain the Storybook addon configuration.
options.alias Object Assigns Babel module-resolver aliases to the Storybook instance.
options.builderDirectory String Defines the Twing instance directory .twing that can be used to include custom Twing functionality.
options.configDirectory String Defines the Storybook instance directory for your theme: ./.storybook.
options.globalMode Boolean/String experimental This will use a global render context for Twig.
options.optimization Object Defines the Webpack optimization configuration.
options.staticDirectory String Defines the destination path for the production build of the Storybook styleguide storybook-static.
options.useLegacyCompiler Boolean Enables the usage of older Twing libraries within the styleguide to disable the requirement of async stories.

Watcher configuration

The Watcher can be started by defining the watch parameter to the CLI and will run the defined hooks from the TaskManager. The Watcher will shutdown automatically if no event occured during the defined duration.

Option type Description
instances Object[String, Object] Spawns a Wacther instance for each defined entry.
instances[].event String Defines the Event handler and will publish the defined hook with the TaskManager.
instances[].path String/String[] Watches the given paths for the spawned Watcher.
instances[].workers String[] Will publish the defined Harbor workers in order.
options Object Optional configuration for the Watcher class.
options.delay number Creates a timeout before running the connected Workers after a Watch event has occured.
options.duration number Defines the lifetime in miliseconds of the spawned Watcher instances.

Example NPM script setup

You can assign the following NPM script entries when using the default hook configuration:

  {
    "production": "node node_modules/@toolbarthomas/harbor/index.js --task=prepare,compile --minify",
    "predevelopment": "npm run production",
    "development": "node node_modules/@toolbarthomas/harbor/index.js --watch --styleguide",
    "images": "node node_modules/@toolbarthomas/harbor/index.js --task=images",
    "javascripts": "node node_modules/@toolbarthomas/harbor/index.js --task=javascripts",
    "resolve": "node node_modules/@toolbarthomas/harbor/index.js --task=resolve",
    "styleguide": "node node_modules/@toolbarthomas/harbor/index.js --styleguide",
    "stylesheets": "node node_modules/@toolbarthomas/harbor/index.js --task=stylesheets",
    "test": "node node_modules/@toolbarthomas/harbor/index.js --task=test",
  }

Asset management

Assets can be included by using the attach_library Twig function within your templates.

Stylesheets that are injected from the attach_library function will also refresh during a file change.

You need a valid Drupal theme library configuration file within your theme with the defined resources you want to use within the theme:

# example.libraries.yml
base:
  version: 1.x
  css:
    base:
      dist/main/stylesheets/index.css: {}
  js:
    dist/main/javascripts/base.js: {}

The defined assets will be included within the templates that uses the attach_library Twig function:

{{ attach_library('example/base') }}

It also possible to use the defined Storybook preview head & body snippets within your project. Harbor will copy the configuration files from the defined configDirectory option within the StyleguideCompiler plugin ('./storybook').

You can also import the actual assets within each storybook story to enable Hot Module Reload. Keep in mind that you still need to define the required libraries within Drupal if you don't include assets with the attach_library function.

// example.stories.js

import styles from './styles.css';

...

Suggested javascript structure.

Using the functionality of the Drupal.behaviors Object, you can run the defined javascript during the (initial) (re)load within Drupal and Storybook. Storybook calls the attach handler within the Drupal behaviors, this Object is used within Drupal sites and is also available for the styleguide.

The actual javascript can be created like the following and should be compliant with the Drupal javascript structure:

  (function example(Drupal) {
    Drupal.behaviors.example = {
      attach: (context, settings) => {
        ...
      }
    }
  })(Drupal, drupalSettings);

Implementing templates since >=0.700.0

The Storybook environment has been upgraded to V7 since version 0.700.0. Some modifications are required in order correctly render your Harbor composed stories. If you are currently using Harbor below 0.100.0 then you also need replace the CommonJS requires with the ESM supported import syntax.

Note: There is currently an issue with the SVG related Twig functions since they don't output the width and height attributes within the Twig render. Setting these values are not required according to the W3C specification. Setting these attributes conflict with the method the elements are implemented; "as inline SVG sprite images that can is styled according to the css". You can safely ignore these errors/warnings since they should not affect the render result. You should file an issue within the Harbor repository if you still encounter an issue within 0.700.0+

// example.stories.js

import Template from 'template.twig';

export default {
  title: 'Example template',
  loaders: [
    async ({ args }) => {
      Template: await Template(args); // Keyname can be anything.
    },
  ],
};

export const Template = {
  // Before 0.700.0 we would use the actual render handler directly within our named export definition.
  render: (args, { loaded }) => loaded.Template,
}
// Instead of:
// export const Template = (args, { loaded }) => loaded.Template.

Implementing templates since >=0.100.0 && <=0.300.0

As of version 0.100.0 you need to define your Twing templates within the Storybook loaders configuration. This is required in order to display the actual templates; since they are rendered in asynchronous order:

// example.stories.js

import Template from 'template.twig';

export default {
  title: 'Example template',
  loaders: [
    async ({ args }) => {
      Template: await Template(args); // Keyname can be anything.
    },
  ],
};

// loaded.Templates is defined within the default default export.
export const Default = (args, { loaded }) = > loaded.Template;

// Define the actual arguments
Default.args = {
  title: 'Foo',
};
{{ title }}

Usage of SVG Inline Sprites

Harbor compiles the defined SVG images with the SVGSpriteCompiler and these can be used within the Twig templates. The sprites are available as a Storybook Global that can be accessed within the styleguide.

You can easily include these paths with the add_svg Twig function. This will output the path of an inline SVG sprite that has been created by the SVGSpriteCompiler. This function accepts 3 arguments to output the path of your selection:

{{ add_svg('svg--chevron--down') }}

This will include the path of the SVG sprite based from the first inline svg that has been stored within the THEME_SPRITES storybook global. This would output dist/main/images/svgsprite.svg#svg--chevron--down if the entry key would be defined as svgsprite...

You can use any entry key of the SVGSpriteCompiler configuration to use that specific sprite path instead:

  // harbor.config.js

  workers: {
    ...
      SvgSpriteCompiler: {
        ...
        entry: {
          common: 'main/images/common/**.svg',
          icons: 'main/images/icons/**.svg',
        },
        ...
    ...
  }
{{ add_svg('svg--chevron--down', 'icons') }}

This will output dist/main/images/icons.svg#svg--chevron--down.

It is also possible to output the base SVG element when the second or third argument has been defined as true:

{{ add_svg('svg--logo', true) }}

Would output:

<svg aria-hidden="true" aria-focusable="false">
  <use xlink:href="dist/main/images/common.svg#svg--logo"></use>
</svg>

And:

  {{ add_svg('svg--chevron--down', 'icons', true) }}

Will render:

<svg aria-hidden="true" aria-focusable="false">
  <use xlink:href="dist/main/images/icons.svg#svg--chevron--down"></use>
</svg>

Running Snapshot tests

It is possible to run Snapshot tests with BackstopJS for all created Storybook stories. Storybook first generates a stories manifest in order to define the components to test. A temporary Storybook instance will be created afterwards, which BackstopJS will use for the snapshot tests.

You need to enable reference snapshots first otherwise you will encounter an error, you need to pass the optional test parameter within the command:

$ harbor --task=test --test=reference

This will create reference snapshots within the defined options.backstopJS.bitmaps_reference configuration option. These snapshots will tested with the defined testing snapshots afterwards:

# You don't need to define the `test` parameter since this is the default testing method.
$ harbor --task=test --test=test

An exception will be thrown if there are any mismatches with the references. You can approve these changes automatically by running:

$ harbor --task=test --test=approve

Readme

Keywords

Package Sidebar

Install

npm i @toolbarthomas/harbor

Weekly Downloads

204

Version

0.800.0

License

MIT

Unpacked Size

222 kB

Total Files

90

Last publish

Collaborators

  • toolbarthomas