GithubHelp home page GithubHelp logo

bem-sdk-archive / bem-deps Goto Github PK

View Code? Open in Web Editor NEW
10.0 12.0 5.0 289 KB

🎯 Manage BEM dependencies. DEPRECATED →

Home Page: https://github.com/bem/bem-sdk/tree/master/packages/deps

License: Other

JavaScript 100.00%

bem-deps's Introduction

bem-deps

NPM Status Travis Status Windows Status Coverage Status Dependency Status

Install

$ npm install --save-dev @bem/deps

Usage

var bemDeps = require('@bem/deps'),
    toArray = require('stream-to-array');

toArray(bemDeps.load({ levels: ['blocks'] }), function (err, relations) {
    var declaration = [{ block: 'a' }],
        res = bemDeps.resolve(declaration, relations, { tech: 'js' });

    console.log(JSON.stringify(res, null, 4));
});

// {
//     "entities": [
//         { "block": "c" },
//         { "block": "a" },
//         { "block": "b" }
//     ],
//     "dependOn": [
//         {
//             "tech": "bemhtml",
//             "entities": [
//                 { "block": "d" }
//             ]
//         }
//     ]
// }

License

Code and documentation copyright 2015 YANDEX LLC. Code released under the Mozilla Public License 2.0.

bem-deps's People

Contributors

blond avatar qfox avatar shkarupa-alex avatar skad0 avatar swinx avatar yeti-or avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bem-deps's Issues

Make decomposition of issues for contributors

It will be cool if you make decomposition of epic issues to some little issues.

For example, I want to contribute to the project but I have not much time for the epic tasks. And if there will be little tasks that easy to understand, I will do some of them.

Mods as array of strings

{
    "shouldDeps": [
        {
            "block": "block",
            "mods": [
                "mod-1",
                "mod-2"
            ]
        },
        {
            "block": "block",
            "elem": "elem",
            "mods": [
                "mod-1",
                "mod-2"
            ]
        }
    ]
}

Преобразуется в

[
    {
        "block": "block"
    },
    {
        "block": "block",
        "mod": "0",
        "val": true
    },
    {
        "block": "block",
        "mod": "1",
        "val": true
    },
    {
        "block": "block",
        "elem": "elem"
    },
    {
        "block": "block",
        "elem": "elem",
        "mod": "0",
        "val": true
    },
    {
        "block": "block",
        "elem": "elem",
        "mod": "1",
        "val": true
    },
    {
        "block": "block",
        "mod": "0",
        "val": "mod-1"
    },
    {
        "block": "block",
        "mod": "1",
        "val": "mod-2"
    },
    {
        "block": "block",
        "elem": "elem",
        "mod": "0",
        "val": "mod-1"
    },
    {
        "block": "block",
        "elem": "elem",
        "mod": "1",
        "val": "mod-2"
    }
]

Graph API

I think we should abandon internal format.

API with internal format

const resolve = require('bem-deps').resolve;

const decl = [{ block: 'A' }];
const deps = [
    {
        entity: { block: 'A' },
        dependOn: [
            {
                entity: { block: 'B' }
            }
        ]
    },
    {
        entity: { block: 'B' },
        dependOn: [
            {
                entity: { block: 'C' },
                order: 'dependenceBeforeDependants'
            }
        ]
    },
    {
        entity: { block: 'C' },
        tech: 'js',
        dependOn: [
            {
                entity: { block: 'D' },
                tech: 'bemhtml'
            }
        ]
    }
];

const resolved = resolve(decl, deps);

Graph API

const DependencyGraph = require('bem-deps').DependencyGraph;
const graph = new DependencyGraph();

graph
    .node({ block: 'A' })
    .addDependency({ block: 'B' });

graph
    .node({ block: 'B' })
    .addDependency({ block: 'C' }, { ordered: true });

graph
    .node({ block: 'C' }, { tech: 'js' })
    .addDependency({ block: 'D' }, { tech: 'bemhtml' });

graph.dependenciesOf({ block: 'A' }, { tech: 'js' });

Methods:

  • graph.node(entity[, { tech: string }]) — gets or adds BEM entity part to graph.

    Returns GraphNode instance.

    If tech is not specified, adds entity part with common (*) tech.

  • graph.addDependency(dependence[, { tech: string, ordered: boolean }]) — add dependency from node entity part to dependence entity part.

    If tech is not specified, adds dependency to dependence with common (*) tech.

    If ordered: true, adds note that dependence will be added before dependants (old order: dependenceBeforeDependants).

  • graph.dependenciesOf(entity[, { tech: string }) — resolve dependencies for specified tech.

    Returns { entities: iterator, dependOn: object }.

    If tech is not specified, dependencies will be resolve for common tech.

  • graph.entityParts({ tech: string }) — returns all entity parts in graph for specified tech.

    Returns iterator with entity parts.

    If tech is not specified, returns entity parts with common tech.

  • graph.sortedEntityParts({ tech: string }) — like graph.sortedEntityParts(), but sorted by dependency rules (like graph.dependenciesOf).

    Equivalently graph.dependenciesOf(graph.entityParts()).

Why?

  • The Graph API looks easier.
  • We should not create data in special format to resolve dependencies.
  • We can faster to parse other formats.
  • We can parse all deps once and resolve many times (before we should parse inner format every time we had resolve dependencies).
  • We can add helpers like entityParts and sortedEntityParts.

Multi selectors

There are cases when several entities depend on other entity.

The dependency should be added only when both entities are in declaration.

Example:

var bemDeps = require('@bem/deps');

var relations = {
    entity: { block: 'A' },
    onlyWith: [
        { entity: { block: 'B' } }
    ],
    dependOn: [
        { entity: { block: 'C' } }
    ]
};

bemDeps.resolve([{ block: 'A' }, { block: 'B' }], relations);

// [
// { block: 'A' },
// { block: 'B' },
// { block: 'C' }
// ]

bemDeps.resolve([{ block: 'A' }], relations);

// [
// { block: 'A' }
// ]

Real examples:

  1. { block: 'checkbox', modName: 'theme', modVal: 'islands' with { block: 'checkbox', modName: 'type', modVal: 'button' } depend on { block: 'button', modName: 'theme', modVal: 'islands' }.

    islands theme of button should be added only if checkbox has type_button modifier with islands theme.

    Code in bem-componentshttps://github.com/bem/bem-components/blob/5406834d2f35911574ec49f974c0788c800d7835/design/common.blocks/checkbox/_theme/checkbox_theme_islands.deps.js

  2. { block: 'dropdown', modName: 'theme', modVal: 'islands' with { block: 'dropdown', modName: 'switcher', modVal: 'link' } depend on { block: 'link', modName: 'theme', modVal: 'islands' }.

    islands theme of link should be added only if dropdown has switcher_link modifier with islands theme.

    Code in bem-componentshttps://github.com/bem/bem-components/blob/5406834d2f35911574ec49f974c0788c800d7835/design/common.blocks/dropdown/_theme/dropdown_theme_islands.deps.js

Quotes:

Taken from bem-archive/bem-tools#463.

Это позволяет указывать зависимости между сущностями, но без подключения самих сущностей. Например, блок B не зависит от кода блока A напрямую, но если вдруг какой-то другой блок будет явно зависеть от A, то его код (код блока A) должен подключаться раньше подключения кода B. Для этого в B пишется такая зависимость (с ключом include: false), которая влияет только на порядок, без явного подключения самой сущности.


представьте, что есть блок input и у него есть 1) разные темы input_theme_{dark,light} и 2) опциональный элемент input__label

в стилях для темы написано переопределение базовых стилей для лейбла, поэтому input_theme_{dark,light} должен идти после input__label -- т.е. нужен mustDeps: input_theme_{dark,light} mustDeps input__label, но если так написать сейчас, то всегда с темой будет подцепляться input__label (т.к. у нас понятия "это должно идти перед этим" и "это должно попадать в финальную сборку вместе с этим" не разделены)

чтобы можно было указать зависимость по порядку следования в сборке, но при этом не требовать обязательного подключения опционального элемента мы сделали include: false -- input_theme_{dark,light} mustDeps input__label include: false -- в этом случае мы запомним факт про зависимость, но не будем подключать input__label до тех пор, пока кто-то его не попросит без include: false

Specs

  • Add specs for read method
  • Add specs for parse method
  • Add specs for resolve method

Resolve spec -> Input params processing

Need to include following items into this spec:

  • Behavior on empty decl
  • Behavior on empty deps
  • Regular and alternative input format of deps param
  • Regular and alternative input format of opts param

Specs for Resolve

Write specs for resolve method: #2.

Input processing

  • input params processing — #7

Resolving unordered dependencies

  • unordered entities dependencies — #10
  • unordered tech dependencies — #12
  • unordered dependencies recommended ordering — #13

Resolving ordered dependencies

  • ordered entity dependencies — #14
  • ordered tech dependencies — #15
  • ordered dependencies recommended ordering — #16

Resolving dependency cycles

  • Resolving unordered - unordered dependency cycles — #17
  • Resolving unordered - ordered dependency cycles — #18
  • Resolving ordered - ordered dependency cycles — #19

Other

  • Natural BEM entities ordering — #20
  • Ordering priority — #21
  • Ignoring tech dependencies — #22

Unwished elem in fulfilling tenoroks

https://github.com/bem-sdk/bem-deps/blob/master/lib/formats/deps.js/reader.js#L24

Can be fixed with swapping lines and adding entity.block || to entity.elem || (…) like that:

                dep[depsType].forEach(function (entity) {
                    entity.block || entity.elem || (entity.elem = file.entity.elem);
                    entity.block || (entity.block = file.entity.block);
                });

Otherwise it adds unwished elem to dependent blocks:

({
    block: 'header',
    elem: 'logo',
    shouldDeps: [{
        block: 'logo' // It will require now logo__logo
    }]
});

Resolve spec -> Ordering priority

описывает приоритет упорядочивания зависимостей
Describes priority between ordering rules (recommended ordering for unordered deps, BEM natural ordering, ordered dependencies)

Expose simple API methods

normalize([{ ?tech, ?mustDeps, ?shouldDeps }]): tenorok[]  normalize internals
fulfill({ ?elem, mod, val }, { block, ?elem }): tenorok  reader internals

And also the current functionality for streams, and for https://nda.ya.ru/3RfcVx

Proposal: optionalMustDeps for optional entities

There are cases when we have a block which have a few optional modifiers. These modifiers can be included to bundle at the same time or separately. And in the case when they are included together the order becomes important.

I'm propose feature like include: false, but more understandable for community.

Consider the example.
Block filter is just a button in the common case.

block('filter').content()({ block: 'button' });

It have optional ability to show tooltip on mouseover. Code for this feature locate in the modifier _hoverable_yes. Template of this modifier adds to content the tooltip.

block('filter').mod('hoverable', 'yes').content()(function() {
  return [
    applyNext(),
    { block: 'tooltip' }
  ];
});

Also block have other optional ability to turn into dropdown. Code for this feature locate in the modifier _changable_yes. Template of this modifier replace content of the main template to dropdown.

block('filter').mod('changable', 'yes').content()({ block: 'dropdown' });

Now, on case when block has both these modifiers order becomes important. We can declare it in the filter_hoverable_yes.deps.js.

({
  shouldDeps: [
    { block: 'tooltip' }
  ],
  optionalMustDeps: [
    { mods: { changable: 'yes' } }
  ]
});

Guys what do you think about it?

Resolve Deps

API

The resolve method to supplement the declaration missing entities.

Important: It takes into account the order dependencies of BEM-entities.

resolve(decl, deps) -> decl

Object[] decl — list of BEM-entities. Each item is object. See, bem-naming.
Object[] deps — list of dependencies between BEM-entities.

Example:

var deps = require('bem-deps'),
    decl = [
        { block: 'A' },
        { block: 'B' }
    ],
    myDeps = [
        { 
            entity: { block: 'A' },
            dependOn: [
                {
                    entity: { block: 'B' },
                    order: 'dependenceBeforeDependants'
                },
                {
                    entity: { block: 'C' }
                }
            ]
        }
    ];

var res = deps.resolve(decl, myDeps);

console.log(res); 
/*
{ 
    entities: [{ block: 'B' }, { block: 'A' }, { block: 'C' }],
    dependOn: []
}
*/

Terms

  • Common dependencies - dependencies where one entity depends on another entity, without taking in account specific techs
  • Resolve common dependencies - resolve dependencies without specifying any tech
  • Unordered dependencies - dependencies which ordering is not specified explicitly. (i.e. order param is not set)
  • Ordered dependencies - dependencies which ordering specified explicitly (i.e. order param is being set)

Deps Format

Each item of deps list is Object.

Object entity — BEM-entity that is dependent on other
Object[] dependOn — list of BEM-entities of which depends on entity

Example:

{
    entity: { block: 'A' },
    dependOn: [
        { entity: { block: 'B' } },
        { entity: { block: 'C' } }
    ]
}

This means that the block A depends on blocks B and C.

Techs

Constraints can be not only between the BEM-entities but also between technologies.

resolve(decl, deps, { tech: 'css' })

Example:

Tech -> Block

{
    entity: { block: 'A' },
    tech: 'css',
    dependOn: [
        { entity: { block: 'B' } }
    ]
}

This means that the css tech of block A depends on all techs of block B.

Example:

Tech -> Tech

{
    entity: { block: 'A' },
    tech: 'js',
    dependOn: [
        { 
            entity: { block: 'B' },
            tech: 'js'
        }
    ]
}

This means that the js tech of block A depends on js tech of block B.

Deps By Techs

For cases when the technology is dependent on other technology in result will be add dependOn field. Each item of dependOn is object with tech and entities filed.

Example:

var myDeps = {
    entity: { block: 'A' },
    tech: 'js',
    dependOn: [
        { 
            entity: [{ block: 'B' }],
            tech: 'bemhtml'
        }
    ]
};

var res = resolve(decl, deps, { tech: 'js' });

console.log(res);

/*
{
    entities: [{ block: 'A' }],
    dependOn: [
        {
            tech: bemhtml,
            entities: [{ block: 'B' }]
        }
    ]
}
*/

Order

Depending provides information about the order:

  • dependenceBeforeDependants — BEM-entity may require that the other BEM-entity will be before it.
  • false — BEM-entity may expect that the other BEM-entity will be before or after it.

Example:

{
    entity: { block: 'A' },
    tech: 'js',
    dependOn: [
        { 
            entity: { block: 'B' },
            order: 'dependenceBeforeDependants'
        }
    ]
}

This means that the block A depends on of block B and require that B will be before A.

Priority

Rule 1: Try to keep the order of declaration.

Example:

Declaration: A, B, C
Dependency graph: B <- E (with order: dependenceBeforeDependants)
Expected result: A, E, B, C

Rule 2: Try to keep the natural order for BEM-entities with same block scope (block and its mods, elems and their mods):

  • block before others BEM-entities
  • elem after block
  • block mod after block
  • elem mod after elem
  • key-val mod after boolean mod

Prepare infrastructure for tests

Need to add following infrastructure items in order to start working with tests:

  1. Add following modules to dev-dependencies: chai, lodash.
  2. Remove modules from dev-dependencies: must
  3. Add stub for resolve method. Stub needs to be implemented using single entry point style (i.e. need to have index.js as lib entry point)
  4. Remove fake.test.js file.
  5. Rename test folder to spec
  6. Add folder named resolve to spec folder.

Crash on example from readme

/Users/grape/Sources/Yandex/islands/node_modules/@bem/deps/lib/formats/deps.js/reader.js:20
                dep[depsType] = normalize(dep[depsType]);
                                             ^

TypeError: Cannot read property 'mustDeps' of undefined
    at /Users/grape/Sources/Yandex/islands/node_modules/@bem/deps/lib/formats/deps.js/reader.js:20:46
    at Array.forEach (native)
    at /Users/grape/Sources/Yandex/islands/node_modules/@bem/deps/lib/formats/deps.js/reader.js:19:50
    at Array.map (native)
    at /Users/grape/Sources/Yandex/islands/node_modules/@bem/deps/lib/formats/deps.js/reader.js:18:29
    at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:380:3)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.