Sometimes you need to have multiple slightly different variants of the same file, say a different logo for each of your clients, but you want to keep your codebase to one single instance with smaller parts being switched out depending on client, platform, behaviour etc. (a sort of pseudo multitenancy). file-variants is an attempt to solve one part of this problem: using multiple file variants, but only building with one of them at a time.
npm install --save-dev file-variants
Add the following to your package.json file:
{
"scripts": {
"fv-build": "file-variants build"
}
}
Now run npm run fv-build
(or npx file-variants build
)
- Installation
- Running
- Contents
- Usage
- Naming convention
- Parts of a build
- Global config
- Examples
- License
build
build [variant]
common options:
path=path # specify path to search in (relative from cwd)
config=path # specify path to global config (relative from cwd)
include=[,input] # inputs to include
exclude=[,input] # inputs to exclude
override=input,variant # override the variant for a specific input (repeatable)
replace=input,keyword,replacement # replacements for a specific input (repeatable)
global-replace=keyword,replacement # replacements for all inputs (repeatable)
verbose # debug info
Because naming is difficult, please study the file structure below meant to represent the build of an imaginary logo image in order to understand what we've named the different parts of file-variants.
.
+-- _src
| +-- _Logo # input (directory)
| +-- fvi.config.json # config
| +-- foo.png # file variant --\
| +-- bar.png # file variant ---|--> file variant*s*
| +-- foobar.png # file variant --/
| +-- Logo.png # output
| ...
Here each part mentioned in Naming convention will be explained.
- type: directory
- purpose: to contain a file-variant-input (fvi) config and all the file variants of this input.
- type: .json
- purpose: to provide values for file-variants.
NOTE: Configs must always be named "fvi.config.json".
type Variant = string;
type Encoding = 'utf8' | 'utf-8' | 'ascii' | 'base64' | 'binary' | 'hex' | 'latin1' | 'ucs-2' | 'ucs2' | 'utf16le';
interface Config {
default: Variant;
name?: string;
outputName?: string;
encoding?: Encoding;
outPath?: string;
fallbacks?: {
[variant: Variant]: Variant;
};
useGlobalReplacements?: boolean | string[];
marking?: boolean | string;
exclude?: string;
}
default
(required): the default variant to use as fallback if all else fails.name
: name of input. If left empty the input directory's name will be used instead.outputName
: name of output (excluding extension). If left empty,name
will be used instead.encoding
: common encoding of all file variants. If left empty,utf8
will be used. However,encoding
only matters if replacements are being used on the output. If replacements are provided and are to be used,encoding
must be any of the following values:ascii
,utf8
,utf-8
, orlatin1
.outPath
: relative (from input's path) path to place output at.- Unprovided: output will be placed on the same level as input.
- Empty: output will be placed inside input.
- Otherwise: relative from path of input.
fallbacks
: a map of variant to variant fallbacks. If a variant isn't detected inside input, but is detected insidefallbacks
as a key, the corresponding key's value will be used as the variant instead. This will be done recursively until a valid file variant is found, or until no fallbacks remain, in which casedefault
will be used as a variant instead.useGlobalReplacements
: control what keywords should be replaced by global-replace(s) (option).- Unprovided/false: output will not receive any replacements from global-replace(s).
- True: output will use replacements from all found global replaces.
- Array: output will only use replacements found both in global-replace(s) and provided array.
marking
: mark the output to ignore changes more easily.- Unprovided/false: output will not be marked.
- True: will append ".fvo" (not extension) to outputName.
- String: will append given string to outputName OR replace keyword "{marking}" inside outputName.
exclude
: which file variants to exclude (regex). Matches entire filename (including variant).
- type: .* (output will use the same extension as selected file variant)
- purpose: file variant.
- type: .* (same as selected file variant)
- purpose: to be used in your project.
If you need many cli arguments when building, using a global config may be easier. You can specify which global config to use with the config=[path]
argument passed to build
.
Global configs must always be .json-files.
interface GlobalConfig {
values?: {
variant?: string;
};
options?: string[];
overrides?: {
marking?: boolean | string;
};
}
The files for each example can be found under examples/.
You have 3 clients who use your product. You want to keep a shared codebase, where the only difference between these 3 clients is their logo.
.
+-- _src
| +-- _Logo
| +-- fvi.config.json
| +-- foo.png
| +-- bar.png
| +-- foobar.png
| ...
npm run fv-build foobar
Logo.png is now a direct copy of foobar.png. You can now always import Logo.png instead of checking for the current client or doing lazy imports (which require you to build with all logos included).
.
+-- _src
| +-- _Logo
| +-- fvi.config.json
| +-- foo.png
| +-- bar.png
| +-- foobar.png
| +-- Logo.png # copy of src/Logo/foobar.png
| ...
- You use 3 different color.ini [variant-]files.
- You build your project with variant foo.
- You only have colors for a, b, and c.
- Variant foo should not use a (default), but b as its fallback.
.
+-- _src
| +-- _colors
| +-- fvi.config.json
| +-- colors_a.ini
| +-- colors_b.ini
| +-- colors_c.ini
| ...
fvi.config.json
{
"default": "colors_a",
"fallbacks": {
"foo": "colors_b"
},
"marking": true
}
colors_b.ini
[main]
primary=rgba(255, 0, 255, ALPHA_VALUE)
secondary=rgba(0, 255, 0, ALPHA_VALUE)
npm run fv-build foo replace=colors,ALPHA_VALUE,0.5
.
+-- _src
| +-- _colors
| +-- fvi.config.json
| +-- colors_a.ini
| +-- colors_b.ini
| +-- colors_c.ini
| +-- colors.fvo.ini # copy of src/colors/colors_b.ini
| ...
colors.fvo.ini
[main]
primary=rgba(255, 0, 255, 0.5)
secondary=rgba(0, 255, 0, 0.5)
.gitignore
# file-variants outputs
*.fvo*
- You have 3 different splash image resolutions (64x64, 128x128, and 256x256).
- Each variant has 3 resolutions (mentioned above).
- You build your project with variant red.
- You want the outputs to be named splash_RESOLUTION.png.
.
+-- _src
| +-- _splash
| +-- fvi.config.json
| +-- red.x64.png
| +-- red.x128.png
| +-- red.x256.png
| +-- green.x64.png
| +-- green.x128.png
| +-- green.x256.png
| +-- blue.x64.png
| +-- blue.x128.png
| +-- blue.x256.png
| ...
fvi.config.json
{
"default": "red",
"//": "{part0} (pattern {part}\\d*} with only one (1) backslash) means the first part of the",
"//": "filename after variant (ie. red/green/blue in this case).",
"outputName": "{name}_{part0}"
}
npm run fv-build
.
+-- _src
| +-- _splash
| +-- fvi.config.json
| +-- red.x64.png
| +-- red.x128.png
| +-- red.x256.png
| +-- green.x64.png
| +-- green.x128.png
| +-- green.x256.png
| +-- blue.x64.png
| +-- blue.x128.png
| +-- blue.x256.png
| +-- splash_x64.png
| +-- splash_x128.png
| +-- splash_x256.png
| ...
.gitignore
# file-variants outputs
splash*.png
- You have different configs and a README for each.
- You don't want the READMEs to also be outputted.
.
+-- _src
| +-- _config
| +-- fvi.config.json
| +-- A.ini
| +-- A.README.md
| +-- B.ini
| +-- B.README.md
| +-- C.ini
| +-- C.README.md
| ...
fvi.config.json
{
"default": "A",
"exclude": "README"
}
npm run fv-build
.
+-- _src
| +-- _config
| +-- fvi.config.json
| +-- A.ini
| +-- A.README.md
| +-- B.ini
| +-- B.README.md
| +-- C.ini
| +-- C.README.md
| +-- config.ini # No README as output!
| ...
.gitignore
# file-variants outputs
config.ini
MIT. Copyright (c) 2021 Emil Engelin.