Variables
Const ALIAS_DESCRIPTION
ALIAS_DESCRIPTION: unique symbol = Symbol.for('alias_description')
Const ARRAY_SEPARATOR
ARRAY_SEPARATOR: "," = ","
Const ARRAY_TO_DOT_NOTATION_REGEX
ARRAY_TO_DOT_NOTATION_REGEX: RegExp = /\[(\d+)\]/g
Const CONFIGURABLE_KEY
CONFIGURABLE_KEY: "configurable" = "configurable"
Const CONSUMES_KEY
CONSUMES_KEY: "consumes" = "consumes"
Const CORE
CORE: unique symbol = Symbol.for('storfront_core_service')
Const DEFAULT_VARIANT_FIELD
DEFAULT_VARIANT_FIELD: "variants" = "variants"
Const ESCAPE_CHAR
ESCAPE_CHAR: "\" = "\"
Const GBI
GBI: "gbi" = "gbi"
Const GBI_EXPERIENCE
GBI_EXPERIENCE: "gbi_experience" = "gbi_experience"
Const GBI_METADATA
GBI_METADATA: GbTracker.Metadata[] = [{key: GBI,value: 'true'},{key: GBI_EXPERIENCE,value: 'storefront'}]
Const GLOBALS
GLOBALS: object = (typeof global === 'undefined' ? window : global) || {}
Const LOG_PHASES
LOG_PHASES: string[] = [Phase.INITIALIZE,Phase.BEFORE_MOUNT,Phase.MOUNT,Phase.UPDATE,Phase.UPDATED,Phase.BEFORE_UNMOUNT,Phase.UNMOUNT,]
Const LOG_STYLE
LOG_STYLE: "font-weight: bold" = "font-weight: bold"
Const ORIGIN_KEY
ORIGIN_KEY: "origin" = "origin"
Const PAIR_SEPARATOR
PAIR_SEPARATOR: ":" = ":"
Const PROVIDES_KEY
PROVIDES_KEY: "provides" = "provides"
Const RANGE_SEPARATOR
RANGE_SEPARATOR: ".." = ".."
Const RE_RENDER_MESSAGE
RE_RENDER_MESSAGE: "%ctag is preparing to re-render:" = "%ctag is preparing to re-render:"
Const RIOT
RIOT: unique symbol = Symbol.for('storefront_riot_instance')
Const SEPARATORS
SEPARATORS: string[] = [RANGE_SEPARATOR, ARRAY_SEPARATOR, PAIR_SEPARATOR]
Const STORAGE_KEY
STORAGE_KEY: string = "gb-previous-search-terms"
Const STYLISH_CLASS
STYLISH_CLASS: "gb-stylish" = "gb-stylish"
Const STYLISH_CLASS
STYLISH_CLASS: "gb-stylish" = "gb-stylish"
Const SUGAR_EVENTS
SUGAR_EVENTS: string[] = [Phase.BEFORE_MOUNT,Phase.MOUNT,Phase.UPDATE,Phase.UPDATED,Phase.BEFORE_UNMOUNT,Phase.UNMOUNT,]
Const TAGS
TAGS: unique symbol = Symbol.for('storefront_tags')
Const TAG_DESC
TAG_DESC: unique symbol = Symbol.for('tag_description')
Const TAG_META
TAG_META: unique symbol = Symbol.for('tag_metadata')
Const TRACKER_EVENT
TRACKER_EVENT: "tracker:send_event" = "tracker:send_event"
Const UI_CLASS
UI_CLASS: "gb-ui" = "gb-ui"
Const UI_CLASS
UI_CLASS: "gb-ui" = "gb-ui"
StoreFront Core
Getting Started
StoreFront's core module can be used to start an instance of StoreFront, mount tags to the DOM and register custom tags. It also contains a number of useful abstractions for the development of custom tags.
Prerequisites
This module is meant to be used in a
node
environment which is bundled for use in the browser.Installing
Use
npm
oryarn
to install in anode
project that useswebpack
,browserify
or similar.npm install --save @storefront/core # or yarn add @storefront/core
Usage
This module can be used both to start an entire StoreFront application, or to create a new component that registers with your application.
Start StoreFront
import StoreFront from '@storefront/core';
// start a StoreFront application const app = new StoreFront({ /* config */ });
// mount a component StoreFront.mount('gb-query'); // or app.mount('gb-query');
Configuration
customerId
: The only required configuration that must be passed to start a StoreFront instanceThe rest of the configuration can be found in the generated API reference.
Use with Webpack
The current minimal webpack configuration to load components from
@storefront
npm packages and link them with @storefront/core.// app.js var StoreFront = require('@storefront/core').default; require('@storefront/structure'); require('@storefront/query');
new StoreFront({ customerId: 'myCustomer' }).mount('*');
// webpack.config.js module.exports = { entry: './app', module: { rules: [{ test: /\.html$/, loader: 'html-loader' }, { test: /\.css$/, use: [ 'to-string-loader', 'css-loader' ] }] } };
Register custom component
Although the supplied development tools do not require ES2015+ to function, the examples will show them with that syntax for cleanliness
import { tag } from '@storefront/core';
const template = '<div>{ someContent }</div>'; // or if storing in separate file const template = require('./my-component.html');
@tag('my-component', template) class MyComponent {
init() { this.someContent = 'hello world!'; } }
Building the package
To build an individual package, run the following command:
yarn build
To build an individual package in response to changes within the
src
directory, run the following command:yarn dev
Running tests
To test an individual packages, run the following command:
yarn test
To test an individual package in response to changes within the
src
andtest
directories, run the following command:yarn tdd
Linting
To lint a package, run the following command:
yarn lint
To programmatically fix lint errors within a package, run the following command:
yarn lint:fix
Contributing
Read the contributing file to learn about how to contribute to the StoreFront project.
License
StoreFront and its related packages are MIT licensed.
Upgrade
Upgrade to version 1.40.0
In order to address issues with performance stemming from using aliases to render deeply nested and complex components, a number of changes have been made to that system and components which use it.
The major conceptual change to the way the system works brings it more inline with the
<Provider>
/<Consumer>
pattern used inReact
's Context API and the language has been changed to match (both for future clarity and to ensure incompatibility with the previous inefficient alias system).Summary:
@alias(aliasName: string)
->@provide(aliasName: string, resolver?: (props, state, aliases) => any)
tag.expose(aliasName: string)
->tag.provide(aliasName: string, resolver?: (props, state, aliases) => any)
@consume(aliasName: string)
tag.consume(aliasName: string)
_consumes
custom component attribute_props
custom component attribute<consume>
custom component<provide>
custom componentitem-props
<gb-list> prop"state-finalized"
tag lifecycle event"recalculate-props"
tag lifecycle event"props-updated"
tag lifecycle event@provide
decoratorThe
@alias
decorator has been removed in preference for the new@provide
decorator.By default, both decorators will register an alias under the name provided and the value will be the
state
of the component.${version} <= v1.39.X
import { alias } from '@storefront/core';
// in TypeScript @alias('myComponent') class MyComponent { state = { myValue: 'Hello, world!' }; }
// or in ES6 @alias('myComponent') class MyComponent { constructor() { this.state = { myValue: 'Hello, world!' }; } }
<!-- in a child component --> <span>{ $myComponent.myValue }</span> <!-- or --> <custom-component> { $myComponent.myValue } </custom-component>
<!-- rendered --> <span>Hello, world!</span>
${version} >= v1.40.0
import { provide } from '@storefront/core';
// in TypeScript @provide('myComponent') class MyComponent { state = { myValue: 'Hello, world!' }; }
// or in ES6 @provide('myComponent') class MyComponent { constructor() { this.state = { myValue: 'Hello, world!' }; } }
Notice that the
@alias
decorator has been replaced with a@provide
decorator which, when provided only a single parameter, works the exact same way as the@alias
decorator.<!-- in a child component --> <span data-is="gb-container" _consumes="myComponent"> { $myComponent.myValue } </span> <!-- or --> <custom-component _consumes="myComponent"> { $myComponent.myValue } </custom-component>
Two changes are made to the "consuming" component:
data-is="gb-container"
_consumes
attribute is added which declares the alias dependencies of the associated component scope<!-- rendered --> <span>Hello, world!</span>
alias resolver
In order to allow aliases to change based on the state of the providing component, the passed either using the
@provide
decorator or thetag.provide()
method must be a function with the following signature:function (props: object, state: object, aliases: object) { return 'my value'; }
This function is called with the
props
,state
andaliases
of the providing component every time a new value is required by a consuming component.@consume()
decoratorWhen creating components using a class, the
@consume()
decorator can be used to statically declare a dependency on an alias. This is similar to using the_consumes
attribute on that component within a template or callingthis.consume()
within that component'sinit()
method.${version} <= v1.39.0
class CustomComponent {}
<!-- in the component's template --> <span>{ $myAlias }</span>
${version} >= v1.40.0
@consumes('myAlias') class CustomComponent {}
<!-- in the component's template --> <span>{ $myAlias }</span>
Note: The important change here is that only components that explicitly "consume" an alias will be able to access the alias value within their template. Because riot's scope is tied only to the direct ancestor component this may mean that a complex component with multiple accesses to an alias may need to be re-thought so that only a single "consumer" is used and then distributed by passing to child components as props or accessing from nested templates using
tag.parent
(also sparingly).${version} <= v1.39.0
<!-- inner-component.tag.html --> <inner-component><yield/></inner-component>
<!-- inner-component.tag.html --> <inner-component><yield/></inner-component>
<!-- app.tag.html --> <app> <some-component> <inner-component someProp="
{ $x }"> This is my innermost content: "{ $y }" </inner-component> </some-component><script> this.expose('x', 'outer value'); this.expose('y', 'inner value'); </script> </app>
${version} >= v1.40.0
<!-- inner-component.tag.html --> <inner-component><yield/></inner-component>
<!-- inner-component.tag.html --> <inner-component> <label>
{ props.someProp }</label> <yield/> </inner-component><!-- app.tag.html -->
<!-- broken implementation --> <app> <some-component _consumes="x,y"> <inner-component some-prop="
{ $x }"> <!-- NOTE: $y is unavailable in this context --> This is my innermost content: "{ $y }" </inner-component> <!-- although it IS available in this context --> </some-component><script> this.provide('x', () => 'outer value'); this.provide('y', () => 'inner value'); </script> </app>
<!-- working implementation --> <app> <some-component consumes="x"> <inner-component some-prop="
{ $x }" consumes="y"> This is my innermost content: "{ $y }" </inner-component> </some-component><script> this.provide('x', () => 'outer value'); this.provide('y', () => 'inner value'); </script> </app>
<!-- alternative implementation --> <app> <some-component _consumes="x,y"> <inner-component some-prop="
{ $x }"> This is my innermost content: "{ parent.$y }" </inner-component> </some-component><script> this.provide('x', () => 'outer value'); this.provide('y', () => 'inner value'); </script> </app>
tag.provide()
andtag.consume()
These methods can be called from within the component's
init()
method only to provide and consume aliases.class MyComponent { init() { this.consume('parentAlias'); this.provide('myAlias', ({ label }, { currentValue }) => ({ label, currentValue })); // note that aliases passed to the resolver do not include the dollar sign ($) prefix this.provide('other', (props, state, { parentAlias }) => ({ label, currentValue, ...parentAlias })); }
onBeforeMount() { // this will throw an exception if called outside of the
<span class="javascript">init()</span>
method this.provide('myAlias', ({ label }, { currentValue }) => ({ label, currentValue })); } }class NestedComponent { init() { this.consume('myAlias'); } onBeforeMount() { // this will throw an exception if called outside of the `init()` method this.consume('myAlias'); } }
_consumes
custom component attributeThis attribute marks a component as a consumer of one or multiple aliases.
<!-- consume a single alias --> <gb-button _consumes="someAlias">{ $someAlias }</gb-button>
<!-- consume multiple aliases --> <gb-button _consumes="someAlias,otherAlias">
{ $someAlias } and { $otherAlias }</gb-button>_props
custom component attributeIn lieu of being able to spread props onto a component (such as in JSX) the
_props
attribute has been added. The object passed will be spread as the props of the component and are overridden by any explicitly passed props.<!-- spread props --> <simple-tag _props="{{ a: 'b', c: 'd', e: 'f' }}"></simple-tag>
<!-- spread props and override --> <simple-tag c="d2" _props="
{{ a: 'b', c: 'd', e: 'f' }}"></simple-tag> <!-- results in props: { a: 'b', c: 'd2', e: 'f' } --><consume>
and<provide>
These components have been added to allow for declarative aliasing but are still rough around the edges as they still introduce a scope and a custom tag into the DOM.
For now, the most useful pattern is to use the
data-is
property on a<virtual>
tag in order to avoid the additional DOM node.<outer-component> <virtual data-is="provide" data="{ someData }" as="someAlias"> <inner-component> <virtual data-is="consume" alias="someAlias"> <child-component>{ $someAlias }</child-component> </virtual> </inner-component> </virtual> </outer-component>
<!-- alternative syntax --> <outer-component> <virtual data-is="provide" data="
{ someData }" as="someAlias"> <inner-component> <child-component _consumes="someAlias">{ $someAlias }</child-component> </inner-component> </virtual> </outer-component><!-- consume multiple --> <virtual data-is="consume" aliases="firstAlias,secondAlias">
{ ... } </virtual><!-- provide multiple --> <virtual data-is="provide" data="
{ someData }" as="firstAlias"> <virtual data-is="provide" data="{ otherData }" as="secondAlias"> { ... } </virtual> </virtual><gb-list>
item-props
attributeIn order to provide props directly to the
<gb-list-item>
components within<gb-list>
theitem-props
attribute has been added. This props is passed into the_props
prop of the<gb-list-item>
tags.<gb-list items="{ [1, 2, 3] }" item-props="{{ a: 'b' }}"> { props.a } { item } </gb-list>
<!-- rendered --> <ul> <li>b 1</li> <li>b 2</li> <li>b 3</li> </ul>
Upgrade to version 1.39.1
In order to address memory usage concerns a new utility method has been added to tag instances which automates the cleanup of
flux
event listeners when the tag is unmounted.${version} <= v1.39.0
class MyComponent { init() { this.flux.on('some_event', this.eventHandler); // usually no cleanup necessary (only executes once) this.flux.one('some_event', this.eventHandler); }
onUnmount() { this.flux.off('some_event', this.eventHandler); } }
${version} >= v1.39.1
class MyComponent { init() { // automatically removed on "unmount" this.subscribe('some_event', this.eventHandler); // alias for this.flux.one() this.subscribeOnce('some_event', this.eventHandler); } }