@smartimpact-it/shopify-disable-features
v0.3.0
Published
Disabled-features engine, analyzer, config helpers, and schema tools for Shopify themes.
Downloads
869
Keywords
Readme
@smartimpact-it/shopify-disable-features
Shared disabled-features engine, unused-features analyzer, config helpers, derived-state helpers, schema generator, and webpack plugins for Shopify themes.
Installation
Install from the public npm registry:
npm install @smartimpact-it/shopify-disable-featuresConsumer usage
Example in a theme repo:
{
"dependencies": {
"@smartimpact-it/shopify-disable-features": "^0.1.0"
}
}Example type usage in theme-disabled-features.mts:
import type { ThemeSchema } from "./available-schema-fields";
import type {
FeatureSetup,
FeatureGroups,
AlwaysEnabledFeatures,
} from "@smartimpact-it/shopify-disable-features";
export const featureGroups = {} as const satisfies FeatureGroups<ThemeSchema>;
export type FeatureGroupNames = keyof typeof featureGroups;
export const featureSetup: FeatureSetup<ThemeSchema, FeatureGroupNames> = {};
export const alwaysEnabledFeatures: AlwaysEnabledFeatures<ThemeSchema> = {};Naming the baked-in settings
Removing a setting that Liquid still reads does not leave a hole: the setting's live value is baked into a variable and every reference is rewritten to it. By default those variables are named after what happened to them:
{% comment %} START ADDED DISABLED FEATURES {% endcomment %}
{%- assign removed_settings_schema_predictive_search_enabled = true -%}
{% comment %} END ADDED BY DISABLED FEATURES {% endcomment %}That is useful in a theme the team maintains and unhelpful in one handed to someone else.
bakedSettingNaming: "neutral" writes the same values under names that read as theme code,
with no marker comments:
{%- assign theme_setting_predictive_search_enabled = true -%}| Flavour | legacy (default) | neutral |
| --- | --- | --- |
| Theme setting | removed_settings_schema_<id> | theme_setting_<id> |
| Section setting | removed_section_settings_<id> | section_setting_<id> |
| Block setting | removed_block_settings_<id> | block_setting_<id> |
| Inline block setting | removed_inline_block_<id> | inline_block_setting_<id> |
| Parent section setting | removed_parent_section_<id> | parent_section_setting_<id> |
Pass an object instead of a preset name to set prefixes individually. A neutral prefix can
collide with a variable the theme already defines, which removed_* never could; when the
name is already in use in that file the legacy name is used for that one variable rather
than shadowing it.
What the collision guard cannot see
The check reads the file being rewritten, and names are reserved across that one file. It
cannot see a variable that arrives from somewhere else: the deprecated {% include %} shares
its caller's scope, so a snippet reached that way could have an injected theme_setting_x
overwrite a caller variable of the same name. {% render %} has its own scope and is not
affected. If a theme still uses include, prefer bakedSettingNaming: 'legacy' there — the
removed_* prefix is deliberately one no theme would write by hand.
Orphan removal (assets and snippets)
Disabling a section or block deletes its .liquid file, but in the si-shopify-base-theme
layout the entity's styles and scripts live in a parallel source tree that webpack
discovers by glob — so they survive the removal and keep being compiled into assets/.
orphanRemoval cleans those up.
export const featureSetup: FeatureSetup<ThemeSchema, FeatureGroupNames> = {
sectionsToRemove: ["editorial__parallax-hero"],
orphanRemoval: {
sourceAssets: true, // src/scss/**, src/sjs/** (default true)
compiledAssets: true, // assets/<prefix>-<name>.{css,js} (default true)
snippets: "zero-reference", // default "off"
},
};For each disabled section and block this removes, when the files exist:
| What | Example for sectionsToRemove: ["hero"] |
| --- | --- |
| Styles | src/scss/sections/hero.scss, src/scss/sections/hero-editor.scss |
| Scripts | src/sjs/sections/hero.ts |
| Compiled chunks | assets/section-hero.css, assets/section-hero.js |
| Sidecars | assets/section-hero.js.map, assets/section-hero.js.LICENSE.txt |
Names map to files the same way wildcards-entry-webpack-plugin builds chunk names: the
path relative to the kind's base directory, without extension, with separators replaced by
__. Nested sources therefore resolve too — src/scss/components/product/quick-add.scss
belongs to component-product__quick-add. Override assetConventions if a theme lays its
sources out differently.
Snippet removal
orphanRemoval.snippets: "zero-reference" deletes every snippet that no surviving Liquid
file renders, iterating to a fixed point so a snippet orphaned by another snippet's removal
is caught too. Component styles and scripts belonging to a removed snippet go with it.
Three safeguards apply:
- Protected names.
assets,script-tags,style-tags,breakpoints,image-size,responsive-image*,responsive-aspect-ratios-cssandsvg.*are build artefacts or the asset-loading facade and are never removed, whatever their reference count. - Unsound-graph bail-out. A
{% render some_variable %}means a snippet could be reached by a name the scan cannot see. When any such tag exists the sweep reports candidates and deletes nothing; list them insnippetsToRemoveto remove them anyway. Shopify's{% render block %}app-block idiom is recognised and does not trip this. - Validation. If a surviving file still renders a removed snippet, the run fails.
Renders are found by walking the whole Liquid AST, not just element children — the base
theme puts renders in HTML attribute position (<div {% render "section__layout" %}>) and
inside {% liquid %} blocks. Renders inside {% comment %} are correctly not references.
Nothing is removed in --comment-out mode, which is non-destructive by contract, and
dry-run reports the same set it would delete.
Protecting specific files
export const alwaysEnabledFeatures: AlwaysEnabledFeatures<ThemeSchema> = {
snippetsToKeepUnchanged: ["legacy-*"],
assetsToKeep: ["src/sjs/sections/*"],
};snippetsToRemove deletes named snippets outright, independently of the sweep.
Build process
This package is published from generated dist/ output.
- source files stay committed in the repo
dist/is generated bynpm run builddist/is gitignored and should not be committed- the published package includes
dist/becausepackage.json -> filespoints to it
That means:
- local repo checkout: run
npm run buildif you want to exercise the built package locally - publish workflow: runs
npm run buildbeforenpm publish - consuming repos: install the package tarball from npm, which already contains
dist/
npm run verify checks that the built export surface can be imported successfully.
Publishing
Release flow:
npm version patch
git push --follow-tagsPushing a vX.Y.Z tag triggers .github/workflows/publish-package.yml, which:
- installs dependencies
- builds
dist/ - verifies the tag matches
package.jsonversion - creates a GitHub release
- publishes to the public npm registry
The workflow uses:
GITHUB_TOKENfor the GitHub releaseNPM_TOKENfor npm publishing
NPM_TOKEN should be an npm automation token with publish access to the @smartimpact-it organization.
Exports
Main exports:
@smartimpact-it/shopify-disable-features@smartimpact-it/shopify-disable-features/config@smartimpact-it/shopify-disable-features/mutations@smartimpact-it/shopify-disable-features/state@smartimpact-it/shopify-disable-features/analyzer@smartimpact-it/shopify-disable-features/feature-disabler@smartimpact-it/shopify-disable-features/schema@smartimpact-it/shopify-disable-features/webpack/schema-definitions-plugin@smartimpact-it/shopify-disable-features/webpack/extension-data-plugin
CLI binaries
Published bins:
shopify-disable-featuresshopify-lint-disabled-featuresshopify-disable-unused-featuresshopify-optimize-disabled-featuresshopify-generate-schema-definitionsshopify-generate-extension-datashopify-generate-theme-features-inventory
