Skip to content

Concepts

Concepts are at the center of Storefront X architecture. In a nutshell, concepts go through directories of enabled modules, and based on their contents, concepts generate the .sfx/ directory.

Concepts are classes that have methods which are executed before each module is loaded (before()), once per module when that module is loaded (run(module)) and after each module is loaded (after()). During this lifecycle, concepts perform their "magic".

Because a lot of functionality is shared between modules, Storefront X has multiple pre-defined concept classes with common functionality. These classes also inherit from each other and this inheritance hierarchy can be seen on the diagram bellow.

Concept hierarchy

IoC concept

IoC concept is the most simple and most common concept.

It requires files inside concept directories to use default export.

Extending concept

Enables extending functionality.

More information about this functionality can be found here.

Generating concept

Generates only one file. This one generated file re-exports default exports of files inside concept directories as a single, default exported, object. This file is generated inside the .sfx/ directory and has the same name as the concept directory upon which this concept operates.

Example concept

module-a/concepts/ServerRoutes.js

js
import { GeneratingConcept } from '@storefront-x/core'

export default class ServerRoutes extends GeneratingConcept {
  get directory() {
    return 'server/routes'
  }
}
import { GeneratingConcept } from '@storefront-x/core'

export default class ServerRoutes extends GeneratingConcept {
  get directory() {
    return 'server/routes'
  }
}

Example source files

module-b/server/routes/B.ts

ts
export default () => 'B'
export default () => 'B'

module-c/server/routes/C.ts

ts
export default () => 'C'
export default () => 'C'

Example generated file

.sfx/server/routes.ts

js
// generated by Storefront X
import B from '~/modules/module-b/server/routes/B'
import C from '~/modules/module-v/server/routes/C'

export default {
  B: B,
  C: C,
}
// generated by Storefront X
import B from '~/modules/module-b/server/routes/B'
import C from '~/modules/module-v/server/routes/C'

export default {
  B: B,
  C: C,
}

Example usage

ts
import serverRoutes from '~/.sfx/server/routes'

console.log(serverRoutes)
import serverRoutes from '~/.sfx/server/routes'

console.log(serverRoutes)

Customizing generated file

You can override the template getter in your concept to customize how the generated file looks like. This getter returns a string which is an ejs template which has access to a records object containing files to be generated.

Example concept

js
// @ts-check

import { GeneratingConcept } from '@storefront-x/core'

export default class AsyncComponents extends GeneratingConcept {
  get directory() {
    return 'asyncComponents'
  }

  get template() {
    return `// generated by Storefront X
import { defineAsyncComponent } from 'vue'

export default {
<%_ for (const [ident, record] of Object.entries(records)) { _%>
  '<%= ident %>': defineAsyncComponent(() => import('<%= record.path %>')),
<%_ } _%>
}
`
  }
}
// @ts-check

import { GeneratingConcept } from '@storefront-x/core'

export default class AsyncComponents extends GeneratingConcept {
  get directory() {
    return 'asyncComponents'
  }

  get template() {
    return `// generated by Storefront X
import { defineAsyncComponent } from 'vue'

export default {
<%_ for (const [ident, record] of Object.entries(records)) { _%>
  '<%= ident %>': defineAsyncComponent(() => import('<%= record.path %>')),
<%_ } _%>
}
`
  }
}

Example generated file

ts
// generated by Storefront X
import { defineAsyncComponent } from 'vue'

export default {
  A: defineAsyncComponent(() => import('...')),
  B: defineAsyncComponent(() => import('...')),
}
// generated by Storefront X
import { defineAsyncComponent } from 'vue'

export default {
  A: defineAsyncComponent(() => import('...')),
  B: defineAsyncComponent(() => import('...')),
}

Separating client/server code

Sometimes you might wish to separate client/server code. This can be done with the supportsClientServer getter. If this getter is set to true, generating concept will generate two files with .client and .server suffixes. Both of these files will contain normal source files, but .client file will also contain source files with the .client suffix and vice versa for server.

Example concept

js
import { GeneratingConcept } from '@storefront-x/core'

export default class Plugins extends GeneratingConcept {
  get directory() {
    return 'plugins'
  }

  get supportsClientServer() {
    return true
  }
}
import { GeneratingConcept } from '@storefront-x/core'

export default class Plugins extends GeneratingConcept {
  get directory() {
    return 'plugins'
  }

  get supportsClientServer() {
    return true
  }
}

Example source files

my-module/plugins/A.ts

ts
export default () => 'common'
export default () => 'common'

my-module/plugins/B.client.ts

ts
export default () => 'client'
export default () => 'client'

my-module/plugins/B.server.ts

ts
export default () => 'server'
export default () => 'server'

Example generated files

.sfx/plugins.server.ts

ts
// generated by Storefront X
import A from '~/modules/my-module/plugins/A'
import B from '~/modules/my-module/plugins/B.server'

export default {
  B: B,
  C: C,
}
// generated by Storefront X
import A from '~/modules/my-module/plugins/A'
import B from '~/modules/my-module/plugins/B.server'

export default {
  B: B,
  C: C,
}

.sfx/plugins.client.ts

ts
// generated by Storefront X
import A from '~/modules/my-module/plugins/A'
import B from '~/modules/my-module/plugins/B.client'

export default {
  B: B,
  C: C,
}
// generated by Storefront X
import A from '~/modules/my-module/plugins/A'
import B from '~/modules/my-module/plugins/B.client'

export default {
  B: B,
  C: C,
}

Example usage

ts
import pluginsClient from '~/.sfx/plugins.client'
import pluginsServer from '~/.sfx/plugins.server'

console.log(pluginsClient)
console.log(pluginsServer)
import pluginsClient from '~/.sfx/plugins.client'
import pluginsServer from '~/.sfx/plugins.server'

console.log(pluginsClient)
console.log(pluginsServer)

Generating multiple files

Generating concept can also generate multiple files (one generated file per one source file). This can be achieved by setting the generateMultipleFiles getter to true. If done so, the template string will receive a record value instead of records.

Copying concept

Copying concept copies source files to the target directory. This is useful for implementing, for example, the Public concept which exposes static files publicly under URL corresponding to their path/name.

Merging concept

Merging files implements overriding on the basis of objects, not files. So, two files with the same name do not override each other but instead, their default exported objects are merged together.