Repository: bem/bem-core Branch: v5 Commit: 7a9e9327a56d Files: 318 Total size: 1.2 MB Directory structure: gitextract_z09_3irc/ ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .husky/ │ └── pre-commit ├── .nvmrc ├── CHANGELOG.md ├── CHANGELOG.ru.md ├── CLA.md ├── CONTRIBUTING.md ├── CONTRIBUTING.ru.md ├── LICENSE.txt ├── MIGRATION.md ├── MIGRATION.ru.md ├── PLAN.md ├── README.md ├── README.ru.md ├── SUMMARY.md ├── build/ │ ├── platforms/ │ │ ├── desktop.js │ │ └── touch.js │ ├── plugins/ │ │ ├── vite-plugin-bem-levels.js │ │ └── vite-plugin-bem-levels.test.js │ ├── vite.config.js │ └── vite.test.config.js ├── common.blocks/ │ ├── clearfix/ │ │ ├── clearfix.css │ │ ├── clearfix.en.md │ │ ├── clearfix.en.title.txt │ │ ├── clearfix.ru.md │ │ └── clearfix.ru.title.txt │ ├── cookie/ │ │ ├── cookie.en.md │ │ ├── cookie.js │ │ ├── cookie.ru.md │ │ └── cookie.spec.js │ ├── dom/ │ │ ├── dom.deps.js │ │ ├── dom.en.md │ │ ├── dom.js │ │ ├── dom.ru.md │ │ └── dom.spec.js │ ├── events/ │ │ ├── __channels/ │ │ │ ├── events__channels.deps.js │ │ │ ├── events__channels.en.md │ │ │ ├── events__channels.ru.md │ │ │ └── events__channels.vanilla.js │ │ ├── __observable/ │ │ │ ├── _type/ │ │ │ │ ├── events__observable_type_bem-dom.deps.js │ │ │ │ ├── events__observable_type_bem-dom.js │ │ │ │ └── events__observable_type_bem-dom.spec.js │ │ │ ├── events__observable.deps.js │ │ │ ├── events__observable.js │ │ │ └── events__observable.spec.js │ │ ├── events.deps.js │ │ ├── events.en.md │ │ ├── events.ru.md │ │ ├── events.spec.js │ │ └── events.vanilla.js │ ├── functions/ │ │ ├── __debounce/ │ │ │ ├── functions__debounce.spec.js │ │ │ └── functions__debounce.vanilla.js │ │ ├── __throttle/ │ │ │ ├── functions__throttle.spec.js │ │ │ └── functions__throttle.vanilla.js │ │ ├── functions.en.md │ │ ├── functions.ru.md │ │ ├── functions.spec.js │ │ └── functions.vanilla.js │ ├── i-bem/ │ │ ├── __collection/ │ │ │ ├── i-bem__collection.js │ │ │ └── i-bem__collection.spec.js │ │ ├── __internal/ │ │ │ ├── i-bem__internal.ru.title.txt │ │ │ ├── i-bem__internal.spec.js │ │ │ └── i-bem__internal.vanilla.js │ │ ├── i-bem.deps.js │ │ ├── i-bem.en.md │ │ ├── i-bem.en.title.txt │ │ ├── i-bem.ru.md │ │ ├── i-bem.ru.title.txt │ │ ├── i-bem.spec.js │ │ └── i-bem.vanilla.js │ ├── i-bem-dom/ │ │ ├── __collection/ │ │ │ ├── i-bem-dom__collection.deps.js │ │ │ ├── i-bem-dom__collection.js │ │ │ └── i-bem-dom__collection.spec.js │ │ ├── __events/ │ │ │ ├── _type/ │ │ │ │ ├── i-bem-dom__events_type_bem.deps.js │ │ │ │ ├── i-bem-dom__events_type_bem.js │ │ │ │ ├── i-bem-dom__events_type_bem.spec.js │ │ │ │ ├── i-bem-dom__events_type_dom.deps.js │ │ │ │ ├── i-bem-dom__events_type_dom.js │ │ │ │ └── i-bem-dom__events_type_dom.spec.js │ │ │ ├── i-bem-dom__events.deps.js │ │ │ └── i-bem-dom__events.js │ │ ├── __init/ │ │ │ ├── _auto/ │ │ │ │ ├── i-bem-dom__init_auto.deps.js │ │ │ │ └── i-bem-dom__init_auto.js │ │ │ ├── i-bem-dom__init.deps.js │ │ │ ├── i-bem-dom__init.js │ │ │ └── i-bem-dom__init.spec.js │ │ ├── i-bem-dom.deps.js │ │ ├── i-bem-dom.en.md │ │ ├── i-bem-dom.js │ │ ├── i-bem-dom.ru.md │ │ ├── i-bem-dom.spec.js │ │ └── i-bem-dom.tests/ │ │ ├── benchmarks.bemjson.js │ │ └── benchmarks.blocks/ │ │ ├── b1/ │ │ │ ├── b1.deps.js │ │ │ └── b1.js │ │ ├── b2/ │ │ │ ├── b2.deps.js │ │ │ └── b2.js │ │ └── page/ │ │ ├── page.deps.js │ │ └── page.js │ ├── i18n/ │ │ ├── i18n.deps.js │ │ ├── i18n.en.md │ │ ├── i18n.i18n.js │ │ ├── i18n.ru.md │ │ ├── i18n.test.js │ │ ├── i18n.tests/ │ │ │ ├── blocks/ │ │ │ │ ├── logo/ │ │ │ │ │ ├── logo.bemhtml.js │ │ │ │ │ ├── logo.bh.js │ │ │ │ │ ├── logo.deps.js │ │ │ │ │ ├── logo.i18n/ │ │ │ │ │ │ ├── en.js │ │ │ │ │ │ └── ru.js │ │ │ │ │ ├── logo.i18n.js │ │ │ │ │ └── logo.js │ │ │ │ └── page/ │ │ │ │ ├── __js/ │ │ │ │ │ ├── page__js.bemhtml.js │ │ │ │ │ └── page__js.bh.js │ │ │ │ └── page.i18n/ │ │ │ │ ├── en.js │ │ │ │ └── ru.js │ │ │ └── simple.bemjson.js │ │ └── i18n.tmpl-specs/ │ │ ├── 10-simple.bemjson.js │ │ ├── 10-simple.html │ │ └── blocks/ │ │ └── greeting-card/ │ │ ├── greeting-card.bemhtml.js │ │ ├── greeting-card.bh.js │ │ └── greeting-card.deps.js │ ├── identify/ │ │ ├── identify.en.md │ │ ├── identify.ru.md │ │ ├── identify.spec.js │ │ └── identify.vanilla.js │ ├── idle/ │ │ ├── _start/ │ │ │ └── idle_start_auto.js │ │ ├── idle.deps.js │ │ ├── idle.en.md │ │ ├── idle.js │ │ └── idle.ru.md │ ├── inherit/ │ │ ├── inherit.en.md │ │ ├── inherit.ru.md │ │ ├── inherit.spec.js │ │ └── inherit.vanilla.js │ ├── jquery/ │ │ ├── __config/ │ │ │ ├── jquery__config.js │ │ │ └── jquery__config.ru.md │ │ ├── jquery.deps.js │ │ ├── jquery.en.md │ │ ├── jquery.js │ │ ├── jquery.ru.md │ │ └── jquery.ru.title.txt │ ├── keyboard/ │ │ ├── __codes/ │ │ │ └── keyboard__codes.js │ │ ├── keyboard.en.md │ │ └── keyboard.ru.md │ ├── loader/ │ │ ├── _type/ │ │ │ ├── loader_type_bundle.js │ │ │ ├── loader_type_js.js │ │ │ └── loader_type_js.spec.js │ │ ├── loader.en.md │ │ └── loader.ru.md │ ├── next-tick/ │ │ ├── next-tick.en.md │ │ ├── next-tick.ru.md │ │ ├── next-tick.spec.js │ │ └── next-tick.vanilla.js │ ├── objects/ │ │ ├── objects.en.md │ │ ├── objects.ru.md │ │ ├── objects.spec.js │ │ └── objects.vanilla.js │ ├── page/ │ │ ├── __css/ │ │ │ ├── page__css.bemhtml.js │ │ │ └── page__css.bh.js │ │ ├── __js/ │ │ │ ├── page__js.bemhtml.js │ │ │ └── page__js.bh.js │ │ ├── page.bemhtml.js │ │ ├── page.bh.js │ │ ├── page.deps.js │ │ ├── page.en.md │ │ ├── page.en.title.txt │ │ ├── page.examples/ │ │ │ ├── .bem/ │ │ │ │ └── level.js │ │ │ ├── 10-simple.bemjson.js │ │ │ ├── 10-simple.ru.title.txt │ │ │ ├── 10-simple.ru.wiki │ │ │ ├── 20-doctype.bemjson.js │ │ │ └── 20-doctype.ru.title.txt │ │ ├── page.ru.md │ │ ├── page.ru.title.txt │ │ └── page.tmpl-specs/ │ │ ├── 00-empty.bemjson.js │ │ ├── 00-empty.html │ │ ├── 10-simple.bemjson.js │ │ ├── 10-simple.html │ │ ├── 20-style.bemjson.js │ │ ├── 20-style.html │ │ ├── 25-styles.bemjson.js │ │ ├── 25-styles.html │ │ ├── 30-scripts.bemjson.js │ │ ├── 30-scripts.html │ │ ├── 40-nonce.bemjson.js │ │ ├── 40-nonce.html │ │ ├── 60-x-ua-compatible.bemjson.js │ │ ├── 60-x-ua-compatible.html │ │ ├── 70-lang.bemjson.js │ │ └── 70-lang.html │ ├── strings/ │ │ ├── __escape/ │ │ │ ├── strings__escape.spec.js │ │ │ └── strings__escape.vanilla.js │ │ ├── strings.en.md │ │ └── strings.ru.md │ ├── tick/ │ │ ├── _start/ │ │ │ └── tick_start_auto.vanilla.js │ │ ├── tick.deps.js │ │ ├── tick.en.md │ │ ├── tick.ru.md │ │ ├── tick.spec.js │ │ └── tick.vanilla.js │ ├── ua/ │ │ ├── __svg/ │ │ │ ├── ua__svg.bemhtml.js │ │ │ ├── ua__svg.bh.js │ │ │ ├── ua__svg.deps.js │ │ │ ├── ua__svg.en.title.txt │ │ │ ├── ua__svg.ru.title.txt │ │ │ └── ua__svg.tmpl-specs/ │ │ │ ├── 00-simple.bemjson.js │ │ │ └── 00-simple.html │ │ ├── ua.bemhtml.js │ │ ├── ua.bh.js │ │ ├── ua.en.md │ │ ├── ua.en.title.txt │ │ ├── ua.ru.md │ │ ├── ua.ru.title.txt │ │ └── ua.tmpl-specs/ │ │ ├── 00-simple.bemjson.js │ │ └── 00-simple.html │ └── uri/ │ ├── __querystring/ │ │ ├── uri__querystring.deps.js │ │ ├── uri__querystring.spec.js │ │ └── uri__querystring.vanilla.js │ ├── uri.en.md │ ├── uri.ru.md │ ├── uri.spec.js │ └── uri.vanilla.js ├── common.bundles/ │ └── index/ │ ├── blocks/ │ │ └── square/ │ │ ├── _color/ │ │ │ └── square_color_green.css │ │ ├── square.css │ │ ├── square.deps.js │ │ └── square.js │ └── index.bemjson.js ├── common.docs/ │ ├── bemjson/ │ │ ├── bemjson.en.md │ │ └── bemjson.ru.md │ └── i-bem-js/ │ ├── i-bem-js-collections.en.md │ ├── i-bem-js-collections.ru.md │ ├── i-bem-js-common.en.md │ ├── i-bem-js-common.ru.md │ ├── i-bem-js-context.en.md │ ├── i-bem-js-context.ru.md │ ├── i-bem-js-decl.en.md │ ├── i-bem-js-decl.ru.md │ ├── i-bem-js-dom.en.md │ ├── i-bem-js-dom.ru.md │ ├── i-bem-js-events.en.md │ ├── i-bem-js-events.ru.md │ ├── i-bem-js-extras.en.md │ ├── i-bem-js-extras.ru.md │ ├── i-bem-js-html-binding.en.md │ ├── i-bem-js-html-binding.ru.md │ ├── i-bem-js-init.en.md │ ├── i-bem-js-init.ru.md │ ├── i-bem-js-interact.en.md │ ├── i-bem-js-interact.ru.md │ ├── i-bem-js-params.en.md │ ├── i-bem-js-params.ru.md │ ├── i-bem-js-states.en.md │ ├── i-bem-js-states.ru.md │ ├── i-bem-js.en.md │ └── i-bem-js.ru.md ├── desktop.blocks/ │ ├── jquery/ │ │ ├── __config/ │ │ │ ├── jquery__config.deps.js │ │ │ └── jquery__config.js │ │ └── __event/ │ │ └── _type/ │ │ ├── jquery__event_type_winresize.deps.js │ │ └── jquery__event_type_winresize.js │ ├── page/ │ │ ├── __conditional-comment/ │ │ │ ├── page__conditional-comment.bemhtml.js │ │ │ ├── page__conditional-comment.bh.js │ │ │ └── page__conditional-comment.ru.md │ │ ├── page.deps.js │ │ ├── page.examples/ │ │ │ ├── .bem/ │ │ │ │ └── level.js │ │ │ ├── 40-es5-shims.bemjson.js │ │ │ └── 40-es5-shims.ru.title.txt │ │ ├── page.ru.md │ │ └── page.tmpl-specs/ │ │ ├── 50-conditions.bemjson.js │ │ ├── 50-conditions.html │ │ ├── 60-conditional-comments.bemjson.js │ │ ├── 60-conditional-comments.html │ │ ├── 70-custom-x-ua-compatible.bemjson.js │ │ └── 70-custom-x-ua-compatible.html │ └── ua/ │ ├── ua.js │ └── ua.ru.md ├── eslint.config.js ├── jsdoc.config.json ├── package.json ├── playwright.config.js ├── test/ │ ├── browser/ │ │ ├── bemhtml-shim.js │ │ ├── entry.js │ │ ├── index.html │ │ └── modules-shim.js │ ├── browser.spec.js │ └── dist/ │ ├── assets/ │ │ └── test.html │ ├── build-fixtures.js │ ├── config.js │ └── fixtures/ │ ├── chai.js │ ├── desktop.html │ ├── mocha.css │ ├── mocha.js │ ├── sinon-chai.js │ ├── sinon.js │ └── touch.html └── touch.blocks/ ├── page/ │ ├── __icon/ │ │ ├── page__icon.bemhtml.js │ │ └── page__icon.bh.js │ ├── page.bemhtml.js │ ├── page.bh.js │ ├── page.deps.js │ ├── page.ru.md │ └── page.tmpl-specs/ │ ├── 00-empty.html │ ├── 10-simple.html │ ├── 20-style.html │ ├── 25-styles.html │ ├── 30-scripts.html │ ├── 40-nonce.html │ ├── 60-x-ua-compatible.html │ ├── 70-lang.html │ ├── 70-zoom.bemjson.js │ └── 70-zoom.html └── ua/ ├── __dom/ │ ├── ua__dom.deps.js │ ├── ua__dom.js │ └── ua__dom.ru.md ├── ua.bemhtml.js ├── ua.bh.js ├── ua.deps.js ├── ua.js └── ua.ru.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: [master, v*] pull_request: branches: [master, v5] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - run: npm ci - run: npm run lint test: runs-on: ubuntu-latest strategy: matrix: node-version: [20, 22] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: npm - run: npm ci - run: npm test test-browser: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - run: npm ci - run: npx playwright install --with-deps chromium - run: npm run test:browser build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - run: npm ci - run: npm run build - uses: actions/upload-artifact@v4 with: name: dist path: dist/ docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - run: npm ci - run: npm run docs - uses: actions/upload-artifact@v4 with: name: jsdoc path: docs/jsdoc/ ================================================ FILE: .gitignore ================================================ .project .settings .bem/cache .bem/snapshots .enb/tmp libs node_modules coverage.json /coverage *.bundles/*/* !*.bundles/.bem/* !*.bundles/*/blocks !*.bundles/*/*.bemjson.js /dist /docs/jsdoc /test-results /*desktop.docs /*touch.docs /*.examples /*.specs /*.tmpl-specs /*.tests /*.sets _book ================================================ FILE: .husky/pre-commit ================================================ npx lint-staged ================================================ FILE: .nvmrc ================================================ 24 ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## 5.0.0 ### Breaking changes - **ESM-only**: The entire codebase has been migrated from the `ym` module system (`modules.define`/`modules.require`) to native ES modules (`import`/`export`). The `ym` runtime dependency has been removed. - **Vite build system**: ENB and all 17+ associated packages have been replaced with [Vite](https://vite.dev/). A custom `vite-plugin-bem-levels` plugin handles BEM level scanning, `bem:*` virtual module resolution, and redefinition chain barrel generation. - **jQuery 4.0**: The `jquery` peer dependency has been upgraded from `^3.x` to `^4.0.0`. Notable API change: `$.unique()` has been removed — use `$.uniqueSort()` instead. - **Node.js 20+**: The minimum supported Node.js version is now 20 (was 8). - **Native Promises**: `vow` has been removed. All code now uses native `Promise`. - **Native test runner**: Server-side tests now use `node:test` and `node:assert` instead of `mocha`/`chai`. - **Playwright**: Browser tests now use [Playwright](https://playwright.dev/) instead of `mocha-phantomjs` (PhantomJS is dead). - **ESLint 10**: Linting has been migrated from `jshint`/`jscs` to ESLint 10 with flat config (`eslint.config.js`). - **GitHub Actions CI**: Travis CI has been replaced with GitHub Actions. - **Husky + lint-staged**: `git-hooks` has been replaced with `husky` and `lint-staged`. ### Notable changes - All `.vanilla.js` and `.js` source files have been converted to ES modules with `import`/`export` syntax. - Module redefinition chains (e.g., `jquery` with pointer event extensions) are now handled via auto-generated barrel files by `vite-plugin-bem-levels`. - The `i18n` block retains its API but uses native ES modules internally. - Browser spec tests (28 spec files, 500+ test cases) run via Vite dev server + Playwright. - Vite produces platform-specific builds (`desktop`, `touch`) with jQuery as an external dependency. ### Removed packages - **Build**: `enb`, `enb-bem-techs`, `enb-magic-factory`, `enb-magic-platform`, `enb-bemxjst`, `enb-bemxjst-6x`, `enb-bemxjst-7x`, `enb-bemxjst-i18n`, `enb-bh`, `enb-bh-i18n`, `enb-borschik`, `enb-css`, `enb-js`, `enb-bem-docs`, `enb-bem-examples`, `enb-bem-specs`, `enb-bem-tmpl-specs`, `enb-bem-i18n`, `borschik` - **Linting**: `jscs`, `jscs-bem`, `jshint`, `jshint-groups` - **Testing**: `mocha-phantomjs`, `istanbul`, `chai-as-promised` - **Other**: `ym`, `vow`, `bower`, `git-hooks`, `gitbook-api`, `bem-naming`, `bem-walk` ### Removed config files - `.enb/` directory (ENB build config) - `.jshintrc`, `.jscs.json`, `.jshint-groups.js` (linting configs) - `.bowerrc`, `bower.json` (Bower configs) - `.travis.yml` (Travis CI config) - `.githooks/` directory (git-hooks config) ## 4.3.1 ### Bug fixes - Reverted change that led to error when `lazyInit` in `declMod` was used ([#1594](https://github.com/bem/bem-core/pull/1594)). ## 4.3.0 ### Notable changes - `jQuery` was updated to 3.2.1 and 1.12.4 ([#1587](https://github.com/bem/bem-core/pull/1587)). ### Bug fixes - Possibility to force `lazy` initialization from markup was fixed ([#1579](https://github.com/bem/bem-core/pull/1579)). - Possibility to declare entities with mixins was fixed ([#1550](https://github.com/bem/bem-core/pull/1550)). - Now `lazyInit` in `declMod` will throw an error ([#1579](https://github.com/bem/bem-core/pull/1579)). - With bug in `isFunction` method of `functions` block, which works wrong for special functions ([#1577](https://github.com/bem/bem-core/pull/1577)). ### Other changes - `.bemrc` config was added ([#1568](https://github.com/bem/bem-core/pull/1568)). - `vow` was updated to 0.4.17 ([#1565](https://github.com/bem/bem-core/pull/1565)). - `inherit` was updated to 2.2.6 ([#1519](https://github.com/bem/bem-core/pull/1519)). - Private `_delInitedMod` method of `i-bem` block was removed ([#1523](https://github.com/bem/bem-core/pull/1523)). - Removed code for old `bem-tools` (https://github.com/bem/bem-core/commit/e57678b2d64a3b976a53af4a7fa09bf918685821). - npm dependencies were updated ([#1589](https://github.com/bem/bem-core/pull/1589)). - Now tests are executed also on Node.js 8 (https://github.com/bem/bem-core/commit/dd7e5344a64ad1595eab28febb2242134fcedbb3). - More specs for `i-bem-dom` were added ([#1517](https://github.com/bem/bem-core/pull/1517)). - `gitbook` was added ([#1569](https://github.com/bem/bem-core/pull/1569)). - JSDoc was fixed. - Documentation updates. ## 4.2.1 ### Bug fixes - Fixed an issue with elems cache invalidation on DOM modifications ([#1487](https://github.com/bem/bem-core/issues/1487)). - Fixed an issue in `i-bem-dom__events` when event's data was not passed to handler ([#1509](https://github.com/bem/bem-core/pull/1509)). - Fixed method `isEditable` of `dom` module. Missing editable input types were added ([#1502](https://github.com/bem/bem-core/pull/1502)). ### Other changes - Fixed syntax error in `i-bem-dom` JSDoc. - Minor documentation updates. - [CLA](https://github.com/bem/bem-core/blob/v4/CLA.md) introduced. ## 4.2.0 ### Notable changes - `bem-xjst 8.x` support was introduced in BEMHTML templates ([#1486](https://github.com/bem/bem-core/issues/1485)). ### Bug fixes - `concat()` method was fixed in `i-bem-dom__collection` ([#1488](https://github.com/bem/bem-core/issues/1488)). - An issue in `ua__dom` was fix ([#1479](https://github.com/bem/bem-core/issues/1478)). - dist: `i-bem-dom__init_auto` was removed from `no-autoinit` bundle ([#1482](https://github.com/bem/bem-core/issues/1481)). ### Other changes - Now `findChildBlock`, `findChildBlocks`, `findParentBlock`, `findParentBlocks`, `findMixedBlock` and `findMixedBlocks` methods throw an error if block is given as String ([#1469](https://github.com/bem/bem-core/pull/1469/)). - `buildClassName` function was optimized ([#1404](https://github.com/bem/bem-core/pull/1404)). - Docs: English translations were added ([#1483](https://github.com/bem/bem-core/pull/1483), [#1476](https://github.com/bem/bem-core/pull/1476), [#1475](https://github.com/bem/bem-core/pull/1475)). - Migration: Added notes about bemTarget ([#1491](https://github.com/bem/bem-core/issues/1472)). - Migration: added info about template options ([#1467](https://github.com/bem/bem-core/issues/1467)). - Fixed issues in docs. ## 4.1.1 ### Bug fixes — Fixed a bug in `ua` block on `touch.blocks` level ([#1460](https://github.com/bem/bem-core/pull/1460)). ## 4.1.0 ### Bug fixes - Fixed a bug in `identify` returning different result on each call for `document` ([#1441](https://github.com/bem/bem-core/issues/1441)). - `modules.define` recursion problem was fixed ([#1446](https://github.com/bem/bem-core/issues/1446)). - Support for escaping in `ua` block was fixed ([#1435](https://github.com/bem/bem-core/issues/1435)). - Workaround for `Array.prototype.push` bug in `Opera 41` was implemented. - An issue with pointer events on iOS devices was fixed ([#1253](https://github.com/bem/bem-core/issues/1253)). - An issue in `i-bem-dom__events` was fixed. Method `once()` was broken in some cases ([#1452](https://github.com/bem/bem-core/issues/1452)). ### Other changes - Ability to specify `html@lang` attribute was added to `page` block ([#751](https://github.com/bem/bem-core/issues/751)). ## 4.0.0 ### Breaking changes - Changes in the `i-bem` block. See [MIGRATION.md](MIGRATION.md#changes-in-the-i-bem-block). - Changes in the `querystring`. See [MIGRATION.md](MIGRATION.md#changes-in-the-querystring-block). - The optional parameter `onlyGet` was removed from the `identify` module ([#1028](https://github.com/bem/bem-core/issues/1028)). - All static methods were removed from the `events` module ([#1024](https://github.com/bem/bem-core/issues/1024)). - The `result` field in `Event` class of `events` module was removed ([#1023](https://github.com/bem/bem-core/issues/1023)). - The `css` element of the `page` block does not support auto insertion of conditional comments for IE ([#379](https://github.com/bem/bem-core/issues/379)). ## 3.2.0 ### Bug fixes - `modules.define` recursion problem was fixed ([#1446](https://github.com/bem/bem-core/issues/1446)). - Support for escaping in `ua` block was fixed ([#1435](https://github.com/bem/bem-core/issues/1435)). - Workaround for `Array.prototype.push` bug in `Opera 41` was implemented. ### Other changes - Ability to specify `html@lang` attribute was added to `page` block ([#751](https://github.com/bem/bem-core/issues/751)). ## 3.1.0 ### Bug fixes - An issue in `getMods()` method of `i-bem` was fixed ([#1379](https://github.com/bem/bem-core/issues/1379)). ### Notable changes - Templates: support for escaped mode was introduced ([#1406](https://github.com/bem/bem-core/issues/1406)). ### Other changes - Minor documentation updates. ## 3.0.1 ### Bug fixes - An issue with pointer events on iOS devices was fixed ([#1253](https://github.com/bem/bem-core/issues/1253)). ## 3.0.0 ### Breaking changes - Base templates for `BEMHTML` and `BEMTREE` were removed ([#1258](https://github.com/bem/bem-core/issues/1258)). `bem-xjst` 6.3.0+ should be used instead. - File extentions of BEMHTML templates were renamed from `*.bemhtml` to `*.bemhtml.js` ([#984](https://github.com/bem/bem-core/issues/984)). Please check that new extention is supported in you build config. - `i-bem__i18n` element was removed ([#1304](https://github.com/bem/bem-core/issues/1304)). Please use `i18n` block for internationalization. - `jquery__events_type_pointerclick` is not using [FastClick](https://github.com/ftlabs/fastclick) anymore ([#1088](https://github.com/bem/bem-core/issues/1088)). ### Notable changes - `jQuery` was updated to 2.2.3 and 1.12.3 ([#1260](https://github.com/bem/bem-core/issues/1260)). ### Bug fixes - An issue in `page` was fixed. `` had wrong `user-scalable` value on the touch level ([#1294](https://github.com/bem/bem-core/issues/1294)). - An issue in `jquery__event_type_pointernative` which led to JS error in IE8 was fixed ([1317](https://github.com/bem/bem-core/issues/1317)). ### Other changes - dist: Autoinitialisation of blocks is optional now ([#1271](https://github.com/bem/bem-core/issues/1271)). ## 2.9.0 ### Notable changes - `jQuery` was updated to 2.2.0 and 1.12.0 ([#1249](https://github.com/bem/bem-core/issues/1249)). ### Bug fixes - Fixed bug in BEMHTML 1.x which leads to drop of `this.mods` in `reapply()` ([#97](https://github.com/bem/bem-xjst/issues/97)). ### Other changes - `jquery__event_type_pointerpressrelease` now exposes `originalEvent` ([#1254](https://github.com/bem/bem-core/issues/1254)). - dist: Support for `i18n` was added to dist ([#1212](https://github.com/bem/bem-core/issues/1212)). - `page__css.bemhtml` template was updated to support new `bem-xjst` versions ([#1228](https://github.com/bem/bem-core/issues/1228)). ## 2.8.0 ### Notable changes - New [i18n](https://github.com/bem/bem-core/tree/v2/common.blocks/i18n) block was introduced, providing support for internationalization ([#1074](https://github.com/bem/bem-core/issues/1074)). - Now jQuery is included via `https` by default ([#1202](https://github.com/bem/bem-core/issues/1202)). - Dependency on `bemhtml-compat` was dropped ([#1186](https://github.com/bem/bem-core/issues/1186)). Users of `bem-tools` need to run `npm i bemhtml-compat --save` to install it on their projects. ### Bug fixes - Bug with undefined handler call in `loader_type_js` was fixed ([#1159](https://github.com/bem/bem-core/pull/1159)). ### Other changes - BH bundles in `dist` now mimic to BEMHTML ([#1210](https://github.com/bem/bem-core/issues/1210)). - `bem create` templates for `bemhtml`, `bemtree`, `vanilla.js` and `browser.js` were improved ([#1183](https://github.com/bem/bem-core/issues/1183)). - `vow` was updated to `0.4.10` ([#1056](https://github.com/bem/bem-core/issues/1056)). ## 2.7.0 ### Notable changes - New `detach` method was added to `i-bem__dom` ([#1102](https://github.com/bem/bem-core/issues/1102)). - `i-bem.bemhtml` now supports nested mixes as objects ([873](https://github.com/bem/bem-core/issues/873)). - Some minor attribute escaping optimizations were added to `i-bem.bemhtml` ([#961](https://github.com/bem/bem-core/issues/961)), ([#980](https://github.com/bem/bem-core/issues/980)) and ([#982](https://github.com/bem/bem-core/issues/982)). - Support for [bem-xjst](https://github.com/bem/bem-xjst) 2.x was added to BEMHTML templates ([#1021](https://github.com/bem/bem-core/issues/1021)). - `clearfix` was optimized to work properly in supported IE browsers ([#722](https://github.com/bem/bem-core/issues/722)). - `jquery` was updated to 2.1.4 and 1.11.3 ([#999](https://github.com/bem/bem-core/issues/999)). ### Bug fixes - An issue in `i-bem__dom` was fixed. `findElem` didn't update cache of elements that had been found previously ([#583](https://github.com/bem/bem-core/issues/583)). - An issue in `i-bem__dom` was fixed. `dropElemCache` worked incorrectly in some edge cases ([#1037](https://github.com/bem/bem-core/issues/1037)). - An issue in `i-bem__dom` was fixed. `setMod` didn't add CSS classes if blocks on the same DOM node had overlapping end parts in their names ([#1090](https://github.com/bem/bem-core/issues/1090)). - An issue in `page` was fixed. `zoom` attribute of the block didn't work for touch levels ([#1020](https://github.com/bem/bem-core/issues/1020)). - An issue in `keyboard__codes` was fixed. `insert` and `delete` keys had wrong key codes ([#1002](https://github.com/bem/bem-core/issues/1002)). - An issue in `i-bem.bemhtml` was fixed. `applyNext` calls were skipped in nested templates ([b1dc50c](https://github.com/bem/bem-core/commit/b1dc50c621b5659cff33daa4dd3f210b67cf25e1)). - An issue in `jquery__events_type_pointernative` was fixed to work properly in IE 11/Edge ([#1066](https://github.com/bem/bem-core/issues/1066)). ### Other changes - Russian documentation for every blocks was reworked. Please visit https://ru.bem.info/libs/bem-core/ for new documentation. - Other minor improvements of the documentation. ## 2.6.0 ### Notable changes - Since now `i-bem__dom` provides module after DOM is ready ([#859](https://github.com/bem/bem-core/issues/859)). - Since now `setMod` and `hasMod` methods of `i-bem__dom` convert their `modVal` argument to string in case it is not of type string or boolean ([#890](https://github.com/bem/bem-core/issues/890)). - An ability to pass `nonce` attribute was added to `page`, to support related parts of Content Security Policy specification ([#882](https://github.com/bem/bem-core/issues/882)). - New `page__conditional-comment` template was added ([#551](https://github.com/bem/bem-core/issues/511)). - `vow` was updated to 0.4.8 ([#837](https://github.com/bem/bem-core/issues/837)). ### Bug fixes - An issue in `i-bem.bemhtml` was fixed. Block CSS class repeated in case of mix with the same block ([#792](https://github.com/bem/bem-core/issues/792)). - An issue in `loader_type_bundle` was fixed. Success callback might be applied after timeout error ([67ff55f](https://github.com/bem/bem-core/commit/da5fdb9923e7e83e3ef9cd31aefc3967ff55fd3c)). - An issue in `i-bem__dom` was fixed. `append`, `prepend` and other similar methods won't properly work with strings in some cases ([#852](https://github.com/bem/bem-core/issues/852)). - An issue in `jquery__event_type_winresize` was fixed. MSIE wasn't detected properly ([#862](https://github.com/bem/bem-core/issues/862)). - An issue in `object` was fixed to proper handle `null` value as `target` argument in `extend` method ([#910](https://github.com/bem/bem-core/issues/910)). - An issue in `page` was fixed. There was no way to disable `x-ua-compatible` meta tag from BEMJSON ([#794](https://github.com/bem/bem-core/issues/794)). ### Other changes - Timeout in `loader_type_bundle` module was increased to 30000 ms ([4e27422](https://github.com/bem/bem-core/commit/000c6af02bfae4506fa460168de16d4e27422393)). - Russian documentation for several blocks was fixed. ## 2.5.1 ### Bug fixes - An issue in `jquery__pointerpress` and `jquery__pointerrelease` was fixed. Events work now in Internet Explorer 8 ([#792](https://github.com/bem/bem-core/issues/792)). - An issue in `jquery__pointernative` was fixed. `pointerenter` and `pointerleave` events have bubbled up to the document root, while they shouldn't ([#801](https://github.com/bem/bem-core/issues/801)). - An issue in `loader_type_bundle` was fixed. CSS bundle has been always added to the top of the HTML ``, so CSS rules from the bundle might not work properly ([#808](https://github.com/bem/bem-core/issues/808)). - Issues in BH templates for `ua` were fixed. There was no possibility to pass the content of the block from BEMJSON ([#734](https://github.com/bem/bem-core/pull/734)). - An issue in `page` was fixed. There was a problem with conditional comments for Internet Explorer in the BH template of the block ([#781](https://github.com/bem/bem-core/pull/781)). ### Other changes - `jquery` was updated to the 2.1.3 and 1.11.2 ([#778](https://github.com/bem/bem-core/pull/788)). - Russian documentation for modules: `clearfix`, `cookie`, `identify`, `idle`, `inherit`, `keyboard`, `loader`, `next-tick`, `string` and `tick` was added. - Russian documentation for `i-bem.js` was updated. - English guides to BEMHTM and BEMJSON were updated. ## 2.5.0 ### Notable changes - bem-core in now published under the [MPL 2.0](https://www.mozilla.org/MPL/2.0/) license ([#443](https://github.com/bem/bem-core/issues/443)). - An ability to specify error handler was added to `loader_type_js` ([#672](https://github.com/bem/bem-core/issues/672)). - `BEMContext` class was added to `oninit` export context in `i-bem.bemtree` ([#602](https://github.com/bem/bem-core/issues/602)). - `reapply` static method was added to BEMContext class of BEMTREE ([#706](https://github.com/bem/bem-core/pull/706)). - bh templates for block `page` were added to touch level ([#689](https://github.com/bem/bem-core/pull/689)). - [bem-xjst](https://github.com/bem/bem-xjst) was updated to 0.9.0 ([#709](https://github.com/bem/bem-core/pull/709)). ### Bug fixes - An issue in `i-bem__dom` was fixed. `findBlocksInside` could return blocks which weren't inited ([#699](https://github.com/bem/bem-core/issues/699)). - An issue in `tick` was fixed. Timer was not removed by `Tick#stop()` ([#694](https://github.com/bem/bem-core/issues/694)). - An issue in `i-bem.bemhtml` was fixed. `i-bem` CSS class was added to elements by mistake ([#633](https://github.com/bem/bem-core/issues/633)). - `html-from-bemtree` tech was fixed to expose `vow`, `console`, `setTimeout` inside BEMTREE template context ([#438ebb8](https://github.com/bem/bem-core/commit/438ebb8f828e26977592e26511e8aad15176d7a4)). ### Other changes - English guide to BEMJSON was added. - Russian documentation for `querystring` module was added. - Russian documentation for `i-bem.js` was fixed to satisfy current API. - Documentation for BEMHML/BEMTREE for both languages was updated. ## 2.4.0 ### Notable changes - [bem-xjst](https://github.com/bem/bem-xjst) was updated to 0.8.0; [bemhtml-compat](https://github.com/bem/bemhtml-compat) was updated to 0.0.11. ### Bug fixes - An issue in `jquery__event_type_pointerpressrelease` was fixed. `pointerpress`/`pointerrelease` events fired for any press/release of mouse button ([#607](https://github.com/bem/bem-core/issues/607)). - An issue in `i-bem__dom.js` was fixed. Base `live` method was not properly called in some edge cases ([#608](https://github.com/bem/bem-core/issues/608)). ### Other changes - English documentation for JS-syntax of BEMHTML was added. ## 2.3.0 ### Notable changes - New implementation of pointer events was added. Based on pointer events polyfills from [Polymer](http://www.polymer-project.org/) ([#567](https://github.com/bem/bem-core/pull/567)). - Ability to specify additional data for event was added to `bindTo*` methods of `i-bem__dom.js` ([#568](https://github.com/bem/bem-core/issues/568)). ### Other changes - An issue in `i-bem.bemhtml` was fixed. There was an error when mix was used as an object (not an array) in BEMJSON and BEMHTML simultaneously ([#555](https://github.com/bem/bem-core/issues/555)). - An issue in `page` was fixed. There was no possibility to apply standard modes to `page` in BEMHTML template and touch template was broken ([516](https://github.com/bem/bem-core/issues/516)). ## 2.2.4 ### Bug fixes - An issue in `i-bem.js` was fixed. Modifier change event has been emitted even if `beforeSetMod` handler had prevented change ([#546](https://github.com/bem/bem-core/pull/546)). - String decoding process of `querystring__uri` module was fixed to return original string if decode failed ([#554](https://github.com/bem/bem-core/pull/554)). ## 2.2.3 ### Bug fixes - Destruction process of blocks was fixed to prevent unexpected block reinitialization ([#540](https://github.com/bem/bem-core/issues/540)). - An issue in `jquery__event_type_pointer` was fixed. Native mouse events were replaced with pointer events in unexpected cases ([#534](https://github.com/bem/bem-core/issues/534)). - `unbindFrom*` methods of `i-bem__dom` now support multiple events to be passed in arguments ([#533](https://github.com/bem/bem-core/issues/533)). - Lost `functions` dependency in `events` module was restored ([#532](https://github.com/bem/bem-core/issues/532)). ## 2.2.2 ### Bug fixes - An issue with block reinitialization on the DOM node, that has been processed with destructor, was fixed in `i-bem__dom` ([#518](https://github.com/bem/bem-core/issues/518)). - An issue in mod events subscription was fixed in `i-bem`. `false` could be used as `modVal` ([#529](https://github.com/bem/bem-core/issues/529)). - `jquery` was updated to the latest minor releases 2.1.1 and 1.11.1 ([#515](https://github.com/bem/bem-core/issues/515)). ## 2.2.1 - An issue in `jquery__event_type_pointerpressrelease` was fixed. `pointerpress` event has been triggered twice on each mousedown in IE10 ([#505](https://github.com/bem/bem-core/issues/505)). ## 2.2.0 ### Notable changes - New `keyboard__codes` module has been added ([#431](https://github.com/bem/bem-core/issues/431)). - `BEMContext` class was added to oninit export context in `i-bem.bemhtml` ([#485](https://github.com/bem/bem-core/pull/485)). - Ability to declare elements with block class has been added ([#481](https://github.com/bem/bem-core/issues/481)). - Behaviour of `isSimple` method of `BEMContext` was fixed in `i-bem.bemhtml` ([#432](https://github.com/bem/bem-core/pull/432)). - An issue with `liveUnbindFrom` method of `BEMDOM` was fixed in `i-bem__dom` ([#476](https://github.com/bem/bem-core/pull/476)). - An issue with `isFocusable` method of `dom` module was fixed for cases where `domElem` is a link with `tabindex` attribute, but without `href` ([#501](https://github.com/bem/bem-core/issues/501)). - Short way of module declaration was fixed for `i-bem__dom_elem-instances` ([#479](https://github.com/bem/bem-core/issues/479)). - A workaround for rendering performance of blocks initialisation in Chrome-based browsers was added to `i-bem__dom_init_auto` ([#486](https://github.com/bem/bem-core/issues/486)). - `vow.js` module has been moved to `vow.vanilla.js` ([#412](https://github.com/bem/bem-core/issues/412)). ### Other changes - `vow` module has been updated to 0.4.3 ([#504](https://github.com/bem/bem-core/pull/504)). - Russian documentation about BEMTREE technology was added ([#500](https://github.com/bem/bem-core/pull/500)). - Russian documentation for JS-syntax of BEMHTML was updated ([#471](https://github.com/bem/bem-core/pull/471)). - API references for JS-modules has been added as a separate branch `v2-jsdoc` ([#478](https://github.com/bem/bem-core/pull/478)). ## 2.1.0 ### Notable changes - An issue in `i-bem.js` when modifiers change event had been emitted before `onSetMod` handlers have been called was fixed ([#454](https://github.com/bem/bem-core/issues/454)). - An issue in `i-bem.bemhtml` was fixed. Since now `this.mods` and `this.ctx.mods` use the same object ([#441](https://github.com/bem/bem-core/issues/441)). - Error in modular declaration of element modifiers was fixed in `i-bem__dom_elem-instances` ([#447](https://github.com/bem/bem-core/issues/447)). - [inherit](https://github.com/dfilatov/inherit) module was updated to 2.2.1 ([#466](https://github.com/bem/bem-core/issues/466)). - An order of tags in `head` section of `page.bemhtml` was fixed ([#465](https://github.com/bem/bem-core/pull/465)). ### Other changes - `baseMix` field description of `i-bem.js` was added to russian docs ([#461](https://github.com/bem/bem-core/pull/461)). - CDN host was changed to `yastatic.net` ([#444](https://github.com/bem/bem-core/issues/444)). Previous CDN host `yandex.st` is still accessible. Physically they both are the same web servers. DNS records is the only difference. - BEMHTML template for `bem create` command was added ([#277](https://github.com/bem/bem-core/issues/277)). - We do not support autobuilding of our tests with Node.js 0.8 in [Travis CI](http://travis-ci.com) any longer ([#455](https://github.com/bem/bem-core/issues/455)). - Travis's build status badge [was changed to SVG version](http://blog.travis-ci.com/2014-03-20-build-status-badges-support-svg/) :) ## 2.0.0 ### Breaking changes - All deprecated methods have been removed from `i-bem.js` and `i-bem__dom.js` ([#318](https://github.com/bem/bem-core/issues/318)). The following methods were removed: * `destruct`, use `onSetMod js ''`; * `extractParams`, use `elemParams`; * `trigger`, use `emit`; * `afterCurrentEvent`, use `next-tick` module; * `channel`, use `events__channels` module; * `changeThis`, use native `Function.prototype.bind`. - `init` and `destruct` events have been removed from `i-bem.js` in favor of modifiers changes events (see "Notable changes" section below). - `ecma` was moved to [separate repo](http://github.com/bem/es5-shims); ES5-shims should be used for IE < 9 ([#230](https://github.com/bem/bem-core/issues/230)). - `vow` module has been updated to 0.4.1 ([#350](https://github.com/bem/bem-core/issues/350)). See [Vow's changelog](https://github.com/dfilatov/vow/blob/0.4.1/CHANGELOG.md) for changes. - Support for vow@0.4 has been added to `i-bem.bemhtml` ([#385](https://github.com/bem/bem-core/issues/385)). ### Notable changes - Support for defining BEMDOM-blocks as [ym](https://github.com/ymaps/modules) modules has been added ([#382](https://github.com/bem/bem-core/issues/382)). - Events for modifiers changes have been added to `i-bem.js` ([#357](https://github.com/bem/bem-core/issues/357)). - Support for passing string values has been added to `BEMDOM.init` ([#419](https://github.com/bem/bem-core/issues/419)). and `BEMDOM.update` methods ([#420](https://github.com/bem/bem-core/issues/420)). - DOM helpers from `i-bem__dom.js` `replace`, `append`, `prepend`, `before`, `after` now return new context and `update` returns updated context as a jQuery object ([#410](https://github.com/bem/bem-core/issues/410)). - New `loader_type_bundle` has been added ([#358](https://github.com/bem/bem-core/issues/358)). - Default jQuery versions were updated to 2.1.0 and to 1.11.0, for IE < 9 ([#356](https://github.com/bem/bem-core/issues/356)). ### Other changes - `i-bem.bemhtml` now uses strings concatination instead of pushing to buffer in it's internals ([#401](https://github.com/bem/bem-core/issues/401)). - jQuery no longer removes itself from global scope if it exists ([#349](https://github.com/bem/bem-core/issues/349)). - `jquery__event_type_pointerclick.js` has been moved from touch level to common ([#393](https://github.com/bem/bem-core/issues/393)). - Modifiers `i-bem_elem-instances_yes` and `i-bem__dom_elem-instances_yes` were renamed to boolean style ([#352](https://github.com/bem/bem-core/issues/352)). - Runtime error in `page` template in development mode has been fixed ([#417](https://github.com/bem/bem-core/issues/417)). - Usage of `Function.prototype.bind` has been droped from `i-bem.js` internals in favor of support for Android 2.3 ([#404](https://github.com/bem/bem-core/issues/404)). - Some bugs in `browser-js+bemhtml` tech have been fixed ([#392](https://github.com/bem/bem-core/issues/392)). - Up to [ym@0.0.15](https://github.com/ymaps/modules/releases) ([#414](https://github.com/bem/bem-core/issues/414)). ## 1.2.0 ### Notable changes - BEM-blocks are emit `destruct` event on destructing ([#370](https://github.com/bem/bem-core/issues/370)). - Improvements of `pointerevents` polyfills ([#354](https://github.com/bem/bem-core/pull/354)). ### Other changes - All JSDocs were fixed so [bem-jsd](github.com/bem/bem-jsd) could parse them ([#335](https://github.com/bem/bem-core/issues/335)). - Russian version of BEMHTML reference was actualized to JavaScript syntax ([#355](https://github.com/bem/bem-core/pull/355)). - Use [bower](http://bower.io) for dependency management ([#367](https://github.com/bem/bem-core/issues/367)). ## 1.1.0 ### Notable changes - `jquery__config` uses jQuery 2.x by default for modern browsers ([#319](https://github.com/bem/bem-core/issues/319)). - Add ability to use any BEMJSON as value of attributes in BEMHTML templates ([#290](https://github.com/bem/bem-core/issues/290)). - Fix dependencies in `i-bem__collection` ([#292](https://github.com/bem/bem-core/issues/292)). - Remove `page` block touch styles ([#306](https://github.com/bem/bem-core/issues/306)). - Fix `page` BEMHTML wrapping in production mode ([#309](https://github.com/bem/bem-core/issues/309)). - Fix possible JavaScript error in script injection in IE<9 in `next-tick` ([#324](https://github.com/bem/bem-core/issues/324)). - Fix `FastClick` initialisation in `jquery__event_type_pointerclick` of `touch.blocks` ([#332](https://github.com/bem/bem-core/issues/332)). - Fix `node.js` tech bug on Windows systems ([#274](https://github.com/bem/bem-core/issues/274)). - Fix `i-bem__dom_elem-instances` bug with `onElemSetMod` ([#340](https://github.com/bem/bem-core/issues/340)). - Use bemhtml from [bem-xjst](https://github.com/bem/bem-xjst) ([#329](https://github.com/bem/bem-core/issues/329)). ### Other changes - [ym](https://github.com/ymaps/modules) was updated to 0.0.12 ([#326](https://github.com/bem/bem-core/issues/326)). - Do not flood `console` with messages if `i-bem__i18n` is not in debug mode ([#285](https://github.com/bem/bem-core/issues/285)). - Fix jsdoc for `dropElemCache()` method of `i-bem__dom` module ([#296](https://github.com/bem/bem-core/issues/296)). - Development infrastructure was updated to [bem-pr@v0.5.x](https://github.com/narqo/bem-pr/blob/0.5.3/HISTORY.md) ([#323](https://github.com/bem/bem-core/issues/323)). - Russian documentation for `i-bem.js` was updated. - [List of supported browsers](https://github.com/bem/bem-core/blob/v1/README.md#supported-browsers) was specified in project README. ## 1.0.0 ### Breaking changes - Starts using modular system [ym](https://github.com/ymaps/modules). - Removes all deprecated methods from `i-bem` and `i-bem__dom`. - `i-bem` now has no dependency on jQuery. `i-bem__dom` still depends on jQuery. - BEMHTML-template can be written with [JS-syntax](https://gist.github.com/veged/6150760). - Introduces new tech `bemtree` (based on [bem-xjst](https://github.com/bem/bem-xjst)) for describing dynamic generation of BEM-tree. - Introduces new tech `vanilla.js` for JS-implementations that does not depend on particular JS-engine. - Introduces new techs `browser.js` and `node.js` for JS-implementations targeted corresponding engines. For backward compatibility we assume that `.js`-files contains `browser.js` implementation. - Introduces polyfill (`jquery__event_type_pointer` and `jquery__event_type_pointerclick` as a jQuery-plugins) for universalize desktop and touch pointer events. - Introduces system for unit testing and blocks examples generation. - Introduces "simple" modifiers (modifiers without value) support in `i-bem` and BEMHTML. ### Other changes - Gets rid of prefixes in all block names (except `i-bem`). - Block `i-bem__dom` becomes a module (in terms of [ym](https://github.com/ymaps/modules)) and all `BEM.DOM`-block must define additions to `i-bem__dom` ([example](https://github.com/bem/bem-core/blob/v1/common.bundles/index/blocks/b-square/b-square.js)). - Method for blocks declaration (`.decl()`) does not accept object with `name` field as first parameter. Required form with `block` field: `BEM.decl({ block: 'b1', modName: 'm', modVal: 'v' }, ...)`. - Introduces `nextTick` method as replacement for `afterCurrentEvent` method for ensure of block existence in callback invocation time. `BEM.afterCurrentEvent` is **deprecated**. - Introduces new `channels` module instead of `BEM.channel`. `BEM.channel` is **deprecated**. - `changeThis` is **deprecated**. Use native `bind` instead. - Removes `del` method from `i-bem` block. - Removes `getWindowSize` method from `i-bem__dom` block. Use `BEMDOM.win.width()` and `BEMDOM.win.height()`. - Introduces `jquery` module-wrapper for providing jQuery. If jQuery already included into the page module-wrapper provides it. Otherwise it loads jQuery (version 1.10.1) on its own. - `$.observable` becomes `events` module and not longer depends on jQuery. - `$.inherit` becomes `inherit` module and not longer depends on jQuery. - `$.identify` becomes `identify` module and not longer depends on jQuery. - `$.throttle` splits into two modules: `functions__throttle` and `functions__debounce`, they both not longer depend on jQuery. - `$.decodeURI`, `$.decodeURIComponent` moves to `querystring__uri` module and not longer depends on jQuery. - `$.cookie` becomes `cookie` module and not longer depends on jQuery. - Introduces `ua` module instead of `$.browser` (with same interface). - Use `pointerclick` instead of `leftclick`. It provides by `jquery__event_type_pointerclick` polyfill. - `i-system` block splits into two modules: `idle` and `tick`. - Triggers for modifiers changes now splitted into two groups: before setting new value (`beforeSetMod` and `beforeElemSetMod`) and after the value has been set (`onSetMod` and `onElemSetMod`). Cancellation of modifiers change is possible only from `before*`-triggers. - Using of `{ onSetMod : { js : function() { ... } } }` is **deprecated**, use `onSetMod: { js : { inited : ... } } }`. - `destruct` method from `i-bem` block is **deprecated**. Use supplementary trigger for `_js` modifiers: `onSetMod: { js : { inited : ... } } }` — `{ onSetMod : { js : { '' : ... } } }`. - `exractParams` method from `i-bem__dom` block is **deprecated**. Use `elemParams` method for access to elements params. - `trigger` method from `i-bem` block is **deprecated** in flavor of `emit` method. - `onFirst` method from `i-bem` block is **deprecated** in flavor of `once` method. - **Deprecated** field `e.block` that provided block-target of BEM-events was removed. Use `e.target` field instead. - Field `e.data.domElem` that provided DOM-element of block in DOM-events was removed. Use `$(e.currentTarget)` (provided by jQuery). - Introduces parameter for `findElem` method that allows to search elements of particular block instance (in case of nested blocks with same name). - Introduces possibility to point particular function in `unbindFrom*` methods. - Introduces `objects` module for work with JS-objects. It contains methods: `extend`, `isEmpty`, `each`. - Introduces `functions` module for work with JS-functions. It contains methods: `isFunction`, `noop`. - Introduces `dom` module for work with DOM-tree. - Introduces `querystring` module for work with URL-based strings. - Introduces `loader_type_js` module for JS loading. - Introduces `vow` module for Promises/A+. - Introduces `next-tick` module as polyfill for `nextTick`, `setImmediate`, `setTimeout(0, ...` and etc. - Introduces `strings__escape` module for XML, HTML and attributes escaping. - `inherit` module now supports mixins. - Introduces `invokeAsap` parameter for `functions__throttle` module that allows to delay first invocation. ================================================ FILE: CHANGELOG.ru.md ================================================ # История изменений ## 5.0.0 ### Несовместимые изменения - **Только ESM**: Вся кодовая база мигрирована с модульной системы `ym` (`modules.define`/`modules.require`) на нативные ES-модули (`import`/`export`). Зависимость `ym` удалена. - **Сборка через Vite**: ENB и все 17+ связанных пакетов заменены на [Vite](https://vite.dev/). Кастомный плагин `vite-plugin-bem-levels` обеспечивает сканирование BEM-уровней, разрешение виртуальных модулей `bem:*` и генерацию barrel-файлов для цепочек переопределений. - **jQuery 4.0**: Peer-зависимость `jquery` обновлена с `^3.x` до `^4.0.0`. Основное изменение API: `$.unique()` удалён — используйте `$.uniqueSort()`. - **Node.js 20+**: Минимальная поддерживаемая версия Node.js — 20 (было 8). - **Нативные промисы**: `vow` удалён. Весь код использует нативный `Promise`. - **Нативный тест-раннер**: Серверные тесты используют `node:test` и `node:assert` вместо `mocha`/`chai`. - **Playwright**: Браузерные тесты используют [Playwright](https://playwright.dev/) вместо `mocha-phantomjs` (PhantomJS мёртв). - **ESLint 10**: Линтинг мигрирован с `jshint`/`jscs` на ESLint 10 с flat-конфигурацией (`eslint.config.js`). - **GitHub Actions CI**: Travis CI заменён на GitHub Actions. - **Husky + lint-staged**: `git-hooks` заменён на `husky` и `lint-staged`. ### Крупные изменения - Все `.vanilla.js` и `.js` файлы конвертированы в ES-модули с синтаксисом `import`/`export`. - Цепочки переопределений модулей (напр., `jquery` с расширениями pointer-событий) обрабатываются автоматически сгенерированными barrel-файлами через `vite-plugin-bem-levels`. - Блок `i18n` сохраняет свой API, но использует нативные ES-модули внутри. - Браузерные спек-тесты (28 файлов, 500+ тестов) запускаются через Vite dev server + Playwright. - Vite генерирует платформенно-специфичные сборки (`desktop`, `touch`) с jQuery как внешней зависимостью. ### Удалённые пакеты - **Сборка**: `enb`, `enb-bem-techs`, `enb-magic-factory`, `enb-magic-platform`, `enb-bemxjst`, `enb-bemxjst-6x`, `enb-bemxjst-7x`, `enb-bemxjst-i18n`, `enb-bh`, `enb-bh-i18n`, `enb-borschik`, `enb-css`, `enb-js`, `enb-bem-docs`, `enb-bem-examples`, `enb-bem-specs`, `enb-bem-tmpl-specs`, `enb-bem-i18n`, `borschik` - **Линтинг**: `jscs`, `jscs-bem`, `jshint`, `jshint-groups` - **Тестирование**: `mocha-phantomjs`, `istanbul`, `chai-as-promised` - **Прочие**: `ym`, `vow`, `bower`, `git-hooks`, `gitbook-api`, `bem-naming`, `bem-walk` ### Удалённые конфигурационные файлы - `.enb/` (конфигурация сборки ENB) - `.jshintrc`, `.jscs.json`, `.jshint-groups.js` (конфигурация линтинга) - `.bowerrc`, `bower.json` (конфигурация Bower) - `.travis.yml` (конфигурация Travis CI) - `.githooks/` (конфигурация git-hooks) ## 4.3.1 ### В релиз вошли следующие исправления ошибок - Откатили изменение, из-за которого при попытке использования `lazyInit` при деклации модификатора выбрасывалось исключение ([#1594](https://github.com/bem/bem-core/pull/1594)). ## 4.3.0 ### Крупные изменения - `jQuery` была обновлена до 3.2.1 и 1.12.4 ([#1587](https://github.com/bem/bem-core/pull/1587)). ### В релиз вошли следующие исправления ошибок - Исправлена возможность форсировать lazy-инициализацию из разметки ([#1579](https://github.com/bem/bem-core/pull/1579)). - Исправлена возможность декларировать сущности с миксинами ([#1550](https://github.com/bem/bem-core/pull/1550)). - Теперь при попытке использования `lazyInit` при деклации модификатора будет выбрасываться исключение ([#1579](https://github.com/bem/bem-core/pull/1579)). - Исправлен баг в методе `isFunction` блока `functions`, который неправильно определял сложные функции ([#1577](https://github.com/bem/bem-core/pull/1577)). ### Также в релиз вошли следующие изменения - Добавлен конфиг `.bemrc` ([#1568](https://github.com/bem/bem-core/pull/1568)). - Блок `vow` обновлен до версии 0.4.17 ([#1565](https://github.com/bem/bem-core/pull/1565)). - Блок `inherit` обновлен до версии 2.2.6 ([#1519](https://github.com/bem/bem-core/pull/1519)). - Удален приватный метод `_delInitedMod` блока `i-bem` ([#1523](https://github.com/bem/bem-core/pull/1523)). - Удален код, относящийся к старой версии `bem-tools` (https://github.com/bem/bem-core/commit/e57678b2d64a3b976a53af4a7fa09bf918685821). - Обновлены npm-зависимости ([#1589](https://github.com/bem/bem-core/pull/1589)). - Теперь тесты в CI запускаются и под Node.js 8 (https://github.com/bem/bem-core/commit/dd7e5344a64ad1595eab28febb2242134fcedbb3). - Добавлены недостающие тесты для блока `i-bem-dom` ([#1517](https://github.com/bem/bem-core/pull/1517)). - Добавлен gitbook ([#1569](https://github.com/bem/bem-core/pull/1569)). - Исправления JSDoc. - Исправления документации. ## 4.2.1 ### В релиз вошли следующие исправления ошибок - Исправлена ошибка с инвалидацией кеша элементов при изменении DOM ([#1487](https://github.com/bem/bem-core/issues/1487)). - Исправлена ошибка в `i-bem-dom__events`, приводившая к тому, что данные события не передавались в обработчик ([#1509](https://github.com/bem/bem-core/pull/1509)). - Исправлен метод `isEditable` модуля `dom`. Добавлены недостающие типы ([#1502](https://github.com/bem/bem-core/pull/1502)). ### Также в релиз вошли следующие изменения - Исправлены синтаксические ошибки в JSDoc блока `i-bem-dom`. - Незначительные исправления документации. - Добавлено [CLA](https://github.com/bem/bem-core/blob/v4/CLA.md). ## 4.2.0 ### Крупные изменения - В BEMHTML-шаблонах реализована поддержка `bem-xjst 8.x` ([#1486](https://github.com/bem/bem-core/issues/1485)). ### В релиз вошли следующие исправления ошибок - Исправлена работа метода `concat()` в i-bem-dom__collection ([#1488](https://github.com/bem/bem-core/issues/1488)). - Исправлена опечатка в `ua__dom`, приводящая к runtime-ошибке при использовании touch-уровня ([#1479](https://github.com/bem/bem-core/issues/1478)). - dist: Исправлена ошибка, при которой `i-bem-dom__init_auto` подключался в `no-autoinit`-бандл ([#1482](https://github.com/bem/bem-core/issues/1481)). ### Также в релиз вошли следующие изменения - Теперь методы `findChildBlock`, `findChildBlocks`, `findParentBlock`, `findParentBlocks`, `findMixedBlock` и `findMixedBlocks` выбрасывают исключение, если блок передан строкой ([#1469](https://github.com/bem/bem-core/pull/1469/)). - Оптимизирована функция построения имен классов ([#1404](https://github.com/bem/bem-core/pull/1404)). - Добавлены переводы на английский ([#1483](https://github.com/bem/bem-core/pull/1483), [#1476](https://github.com/bem/bem-core/pull/1476), [#1475](https://github.com/bem/bem-core/pull/1475)). - Миграционный гайд: добавлено примечание о `bemTarget` ([#1491](https://github.com/bem/bem-core/issues/1472)). - Миграционный гайд: добавлено примечание о необходимых опциях для шаблонизаторов ([#1467](https://github.com/bem/bem-core/issues/1467)). - Исправлены ошибки в документации. ## 4.1.1 ### В релиз вошли следующие исправления ошибок — Исправлена ошибка в блоке `ua` на уровне `touch.blocks`, которая приводила к невозможности предоставить ym-зависимости ([#1460](https://github.com/bem/bem-core/pull/1460)). ## 4.1.0 ### В релиз вошли следующие исправления ошибок - Исправлена проблема в `identify` с возвращением разного результата при вызове для `document` ([#1441](https://github.com/bem/bem-core/issues/1441)). - Исправлена проблема с переполнением глубины стека вызовов `modules.define` ([#1446](https://github.com/bem/bem-core/issues/1446)). - Исправлена поддержка эскейпинга в блоке `ua` ([#1435](https://github.com/bem/bem-core/issues/1435)). - Реализован обход бага в `Array.prototype.push` в `Opera 41`. - Исправлены pointer-события на iOS-устройствах ([#1253](https://github.com/bem/bem-core/issues/1253)). - Исправлена ошибка в `i-bem-dom__events`, приводящая к неправильной работе метода `once()` в некоторых случаях ([#1452](https://github.com/bem/bem-core/issues/1452)). ### Также в релиз вошли следующие изменения - Реализована возможность задавать атрибут `lang` для `` в блоке `page` ([#751](https://github.com/bem/bem-core/issues/751)). ## 4.0.0 ### Изменения, ломающие обратную совместимость - Изменения в блоке `i-bem`. Подробнее в [MIGRATION.ru.md](MIGRATION.ru.md#Изменения-в-блоке-i-bem). - Изменения в блоке `querystring`. Подробнее в [MIGRATION.ru.md](MIGRATION.ru.md#Изменения-в-блоке-querystring). - Из модуля `identify` удален опциональный параметр `onlyGet` ([#1028](https://github.com/bem/bem-core/issues/1028)). - Из модуля `events` удалены все статические методы ([#1024](https://github.com/bem/bem-core/issues/1024)). - В классе `Event` модуля `events` удалено поле `result` ([#1023](https://github.com/bem/bem-core/issues/1023)). - Элемент `css` блока `page` больше не поддерживает автоматическое добавление условных комментариев для IE ([#379](https://github.com/bem/bem-core/issues/379)). ## 3.2.0 ### В релиз вошли следующие исправления ошибок - Исправлена проблема с переполнением глубины стека вызовов `modules.define` ([#1446](https://github.com/bem/bem-core/issues/1446)). - Исправлена поддержка эскейпинга в блоке `ua` ([#1435](https://github.com/bem/bem-core/issues/1435)). - Реализован обход бага в `Array.prototype.push` в `Opera 41`. ### Также в релиз вошли следующие изменения - Реализована возможность задавать атрибут `lang` для `` в блоке `page` ([#751](https://github.com/bem/bem-core/issues/751)). ## 3.1.0 ### В релиз вошли следующие исправления ошибок - Исправлен баг в методе `getMods()` блока `i-bem` ([#1379](https://github.com/bem/bem-core/issues/1379)). ### Крупные изменения - Шаблоны: реализована поддержка режима экранирования ([#1406](https://github.com/bem/bem-core/issues/1406)). ### Также в релиз вошли следующие изменения - Мелкие исправления документации. ## 3.0.1 ### В релиз вошли следующие исправления ошибок - Исправлены pointer-события на iOS-устройствах ([#1253](https://github.com/bem/bem-core/issues/1253)). ## 3.0.0 ### Изменения, ломающие обратную совместимость - Удалены базовые шаблоны и документация для `BEMHTML` и `BEMTREE` ([#1258](https://github.com/bem/bem-core/issues/1258)). Следует использовать версию `bem-xjst` не ниже 6.3.0. - Расширения файлов BEMHTML-шаблонов переименованы с `*.bemhtml` на `*.bemhtml.js` ([#984](https://github.com/bem/bem-core/issues/984)). Необходимо убедиться, что в конфиге сборки поддерживается новое расширение. - Удален элемент `i-bem__i18n` ([#1304](https://github.com/bem/bem-core/issues/1304)). Для интернационализации следует использовать блок `i18n`. - `jquery__events_type_pointerclick` больше не использует библиотеку [FastClick](https://github.com/ftlabs/fastclick) ([#1088](https://github.com/bem/bem-core/issues/1088)). ### Крупные изменения - `jQuery` была обновлена до 2.2.3 и 1.12.3 ([#1260](https://github.com/bem/bem-core/issues/1260)). ### В релиз вошли следующие исправления ошибок - В блоке `page` на уровне `blocks.touch` исправлена ошибка с невалидным значением `user-scalable=0` для `` ([#1294](https://github.com/bem/bem-core/issues/1294)). - Исправлена ошибка в `jquery__event_type_pointernative`, которая приводила к возникновению JS ошибки в IE8 ([1317](https://github.com/bem/bem-core/issues/1317)). ### Также в релиз вошли следующие изменения - dist: Автоматическая инициализация блоков теперь опциональна ([#1271](https://github.com/bem/bem-core/issues/1271)). ## 2.9.0 ### Крупные изменения - `jQuery` была обновлена до 2.2.0 и 1.12.0 ([#1249](https://github.com/bem/bem-core/issues/1249)). ### В релиз вошли следующие исправления ошибок - Исправлена ошибка в `BEMHTML 1.x`, которая приводила к потере `this.mods` в `reapply()` ([#97](https://github.com/bem/bem-xjst/issues/97)). ### Также в релиз вошли следующие изменения - Теперь `jquery__event_type_pointerpressrelease` предоставляет `originalEvent` ([#1254](https://github.com/bem/bem-core/issues/1254)). - dist: Поддержка `i18n` добавлена в dist ([#1212](https://github.com/bem/bem-core/issues/1212)). - Шаблон `page__css.bemhtml` был обновлен для поддержки новых версий `bem-xjst` ([#1228](https://github.com/bem/bem-core/issues/1228)). ## 2.8.0 ### Крупные изменения - Реализован новый блок [i18n](https://github.com/bem/bem-core/tree/v2/common.blocks/i18n), реализующий интернационализацию проектов на bem-core ([#1074](https://github.com/bem/bem-core/issues/1074)). - Теперь `jQuery` по умолчанию подключается через `https` ([#1202](https://github.com/bem/bem-core/issues/1202)). - Удалена зависимость от `bemhtml-compat` ([#1186](https://github.com/bem/bem-core/issues/1186)). Пользователям `bem-tools` необходимо выполнить `npm i bemhtml-compat --save` для установки пакета на уровне проекта. ### В релиз вошли следующие исправления ошибок - Исправлена ошибка в `loader_type_js`, допускавшая вызовы неопределенного обработчика ([#1159](https://github.com/bem/bem-core/pull/1159)). ### Также в релиз вошли следующие изменения - BH-бандлы в `dist` теперь мимикрируют под BEMHTML ([#1210](https://github.com/bem/bem-core/issues/1210)). - Улучшены шаблоны `bem create` для `bemhtml`, `bemtree`, `vanilla.js` и `browser.js` ([#1183](https://github.com/bem/bem-core/issues/1183)). - `vow` обновлена до `0.4.10` ([#1056](https://github.com/bem/bem-core/issues/1056)). ## 2.7.0 ### Крупные изменения - В `i-bem__dom` добавлен новый метод `detach` ([#1102](https://github.com/bem/bem-core/issues/1102)). - В `i-bem.bemhtml` добавлена поддержка вложенных миксов ([873](https://github.com/bem/bem-core/issues/873)). - В `i-bem.bemhtml` добавлены незначительные оптимизации, связанные с эскейпингом аттрибутов ([#961](https://github.com/bem/bem-core/issues/961)), ([#980](https://github.com/bem/bem-core/issues/980)) и ([#982](https://github.com/bem/bem-core/issues/982)). - В BEMHTML-шаблоны добавлена поддержка [bem-xjst](https://github.com/bem/bem-xjst) 2.x ([#1021](https://github.com/bem/bem-core/issues/1021)). - `clearfix` оптимизирован для работы в поддерживаемых библиотекой браузерах IE ([#722](https://github.com/bem/bem-core/issues/722)). - `jquery` обновлен до версий 2.1.4 и 1.11.3 ([#999](https://github.com/bem/bem-core/issues/999)). ### В релиз вошли следующие исправления ошибок - Исправлена ошибка в `i-bem__dom`, из-за которой метод `findElem` не обновлял кэш ранее найденных элементов ([#583](https://github.com/bem/bem-core/issues/583)). - Исправлена ошибка в `i-bem__dom`, приводящая к неправильной работе метода `dropElemCache` в некоторых граничных случаях ([#1037](https://github.com/bem/bem-core/issues/1037)). - Исправлена ошибка в `i-bem__dom`, из-за которой вызов метода `setMod` не выставлял CSS-классы блоку в случае, если на DOM-узеле был подмешан блок с пересекающимся окончанием в имени ([#1090](https://github.com/bem/bem-core/issues/1090)). - Исправлена ошибка в `page`, из-за которой на touch-уровнях не работало специализированное поле `zoom` ([#1020](https://github.com/bem/bem-core/issues/1020)). - Исправлена ошибка в `keyboard__codes`. Клавиши `insert` и `delete` были описаны неправильными кодами ([#1002](https://github.com/bem/bem-core/issues/1002)). - Исправлена ошибка в `i-bem.bemhtml`, из-за которой неверно интерпретировались вложенные вызовы `applyNext` ([b1dc50c](https://github.com/bem/bem-core/commit/b1dc50c621b5659cff33daa4dd3f210b67cf25e1)). - Исправлена ошибка в `jquery__events_type_pointernative`, из-за которой события работали некорректно в браузерах IE 11 и Edge ([#1066](https://github.com/bem/bem-core/issues/1066)). ### Также в релиз вошли следующие изменения - Обновлена русская докуметация для всех блоков библиотеки. Документация доступна по адресу https://ru.bem.info/libs/bem-core/. - Прочие улучшения в документации к библиотеке. ## 2.6.0 ### Крупные изменения - Предоставление модуля `i-bem__dom` теперь происходит после наступления события DOM ready ([#859](https://github.com/bem/bem-core/issues/859)). - Методы `setMod` и `hasMod` модуля `i-bem__dom` теперь явно преобразуют параметр `modVal` к строке, если переданное значение не типа string или boolean ([#890](https://github.com/bem/bem-core/issues/890)). - В блок `page` добавлена возможность прокидывать атрибут `nonce`, для корректной работы инлайн-скриптов, в соответствии со спецификацией Content Security Policy ([#882](https://github.com/bem/bem-core/issues/882)). - Добавлены шаблоны `page__conditional-comment` ([#551](https://github.com/bem/bem-core/issues/511)). - Модуль `vow` обновлен до версии 0.4.8 ([#837](https://github.com/bem/bem-core/issues/837)). ### В релиз вошли следующие исправления ошибок - Исправлена ошибка в `i-bem.bemhtml`, из-за которой CSS-класс блока дублировался, в случае микса с этим же блоком ([#792](https://github.com/bem/bem-core/issues/792)). - Исправлена ошибка в `loader_type_bundle`, из-за которой функция-обработчик успешного результата могла выполняться после наступления таймаута ([67ff55f](https://github.com/bem/bem-core/commit/da5fdb9923e7e83e3ef9cd31aefc3967ff55fd3c)). - Исправлена ошибка в `i-bem__dom`, в некоторых случаях, приводящая к неправильной интерпретации строковых аргументов в методах `append`, `prepend` и др. ([#852](https://github.com/bem/bem-core/issues/852)). - Исправлена ошибка в `jquery__event_type_winresize`, из-за которой неправильно определятся браузер MSIE ([#862](https://github.com/bem/bem-core/issues/862)). - Исправлена ошибка в `object`, из-за которой метод `extend` неправильно обрабатывал `null` в качестве значения аргумента `target` ([#910](https://github.com/bem/bem-core/issues/910)). - Исправлена ошибка в `page`. Из BEMJSON было невозможно отключить добавление meta-тега `x-ua-compatible` ([#794](https://github.com/bem/bem-core/issues/794)). ### Также в релиз вошли следующие изменения - Таймаут в `loader_type_bundle` увеличен до 30000 мс ([4e27422](https://github.com/bem/bem-core/commit/000c6af02bfae4506fa460168de16d4e27422393)). - Исправлены незначительные ошибки в русской документации блоков. ## 2.5.1 ### В релиз вошли следующие исправления ошибок - Исправлена ошибка в `jquery__pointerpress` и `jquery__pointerrelease` из-за которой события не работали в браузере Internet Explorer 8 ([#792](https://github.com/bem/bem-core/issues/792)). - Исправлена ошибка в `jquery__pointernative`. События `pointerenter` и `pointerleave` не должны всплывать по DOM-дереву ([#801](https://github.com/bem/bem-core/issues/801)). - Исправлена ошибка в `loader_type_bundle`. После загрузки, CSS-бандл добавлялся в самый верх HTML-тега ``, из-за чего CSS-правила из содержимого бандла могли работать не корректно ([#808](https://github.com/bem/bem-core/issues/808)). - Исправлена ошибка в BH-шаблоне `ua`. Шаблон не позволял вставить содержимое блока из входного BEMJSON ([#734](https://github.com/bem/bem-core/pull/734)). - Исправлена ошибка в `page`, приводящая к неработоспособности добавленных на страницу условных комментариев для браузера Internet Explorer ([#781](https://github.com/bem/bem-core/pull/781)). ### Также в релиз вошли следующие изменения - `jquery` обновлен до версий 2.1.3 и 1.11.2 ([#778](https://github.com/bem/bem-core/pull/788)). - Добавлена документация на русском языке для модулей: `clearfix`, `cookie`, `identify`, `idle`, `inherit`, `keyboard`, `loader`, `next-tick`, `string` and `tick`. - Исправлена документация на русском языке для `i-bem.js`. - Обновлено руководство на английском языке по технологиям BEMHTML и BEMJSON. ## 2.5.0 ### Крупные изменения - Код библиотеки переведен на использование лицензии [MPL 2.0](https://www.mozilla.org/MPL/2.0/) ([#443](https://github.com/bem/bem-core/issues/443)). - В модуль `loader_type_js` добавлена возможность указывать функцию-обработчик ошибок ([#672](https://github.com/bem/bem-core/issues/672)). - Класс `BEMContext` добавлен в export-параметры функции `oninit` в базовых шаблонах `i-bem.bemtree` ([#602](https://github.com/bem/bem-core/issues/602)). - В `BEMContext` BEMTREE добавлен статический метод `reapply` по аналогии с BEMHTML ([#706](https://github.com/bem/bem-core/pull/706)). - Добавлены bh-шаблоны блока `page` для уровней touch ([#689](https://github.com/bem/bem-core/pull/689)). - npm-модуль [bem-xjst](https://github.com/bem/bem-xjst) обновлен до версии 0.9.0 ([#709](https://github.com/bem/bem-core/pull/709)). ### В релиз вошли следующие исправления ошибок - Исправлена ошибка в `i-bem__dom`, из-за которой метод `findBlocksInside` мог возвращать блоки, которые еще не были инициализированы ([#699](https://github.com/bem/bem-core/issues/699)). - Исправлена ошибка в `tick`, позволявшая вызвать метод `stop` без освобождения внутреннего таймера ([#694](https://github.com/bem/bem-core/issues/694)). - Исправлена ошибка в `i-bem.bemhtml`, из-за которой на элементы блока добавлялся CSS-класс `i-bem` ([#633](https://github.com/bem/bem-core/issues/633)). - Исправлена ошибка в технологии `html-from-bemtree`, из-за которой в контексте BEMTREE-шаблонов не было глобальных объектов `vow`, `console`, `setTimeout` ([#438ebb8](https://github.com/bem/bem-core/commit/438ebb8f828e26977592e26511e8aad15176d7a4)). ### Также в релиз вошли следующие изменения - Добавлено английское руководство по технологии BEMJSON. - Обновлена русская документация для i-bem.js. Теперь документация соответсвует текущему API библиотеки. - Обновлена документация для технологий BEMHTML/BEMTREE. ## 2.4.0 ### Крупные изменения - npm-модуль [bem-xjst](https://github.com/bem/bem-xjst) обновлен до версии 0.8.0; [bemhtml-compat](https://github.com/bem/bemhtml-compat) обновлен до 0.0.11. ### В релиз вошли следующие исправления ошибок - Исправлена ошибка в `jquery__event_type_pointerpressrelease`, из-за которой события `pointerpress` / `pointerrelease` генерировались на нажатие любой кнопки мыши ([#607](https://github.com/bem/bem-core/issues/607)). - Исправлена ошибка в `i-bem__dom.js`, из-за которой в некоторых случаях не происходил вызов базового метода `live` ([#608](https://github.com/bem/bem-core/issues/608)). ### Также в релиз вошли следующие изменения - Добавлена английская документация на JS-синтаксис BEMHTML. ## 2.3.0 ### Крупные изменения - Добавлена новая реализация pointer-событий на основе полифилов из [Polymer](http://www.polymer-project.org/) ([#567](https://github.com/bem/bem-core/pull/567)). - Добавлена возможность в `i-bem__dom.js` указывать дополнительные данные о событии в методах `bindTo*` ([#568](https://github.com/bem/bem-core/issues/568)). ### В релиз вошли следующие исправления ошибок - Исправлена ошибка в `i-bem.bemhtml`, из-за которой было невозможно использовать микс в виде одного объекта (не массива) одновременно в BEMJSON и BEMHTML ([#555](https://github.com/bem/bem-core/issues/555)). - Исправлена ошибка в BEMHTML-шаблоне блока `page`, из-за которой не выполнялись стандартные моды, и исправлена регрессия в шаблоне на touch-уровне ([516](https://github.com/bem/bem-core/issues/516)). ## 2.2.4 ### В релиз вошли следующие исправления ошибок - Исправлена ошибка в `i-bem.js`, из-за которой событие об изменении модификатора генерировалось, даже если обработчик `beforeSetMod` предотвращал изменение ([#546](https://github.com/bem/bem-core/pull/546)). - В случае возникновения ошибки в процессе декодирования строки, модуль `querystring__uri` теперь возвращает оригинальную строку ([#554](https://github.com/bem/bem-core/pull/554)). ## 2.2.3 ### В релиз вошли следующие исправления ошибок - В модуле `i-bem__dom` был исправлен процесс удаления блока для предотвращения нежелательной повторной инициализации блока ([#540](https://github.com/bem/bem-core/issues/540)). - Исправлена ошибка в модуле `jquery__event_type_pointer`, из-за которой нативные события мыши ошибочно замещались на pointer-события ([#534](https://github.com/bem/bem-core/issues/534)). - `unbindFrom*`-методы в модуле `i-bem__dom` теперь поддерживают отписывание от нескольких событий за вызов ([#533](https://github.com/bem/bem-core/issues/533)). - Добавлена недостающая зависимость от модуля `functions` в модуле `events` ([#532](https://github.com/bem/bem-core/issues/532)). ## 2.2.2 ### В релиз вошли следующие исправления ошибок - Исправлена ошибка в модуле `i-bem__dom` приводящая к повторной инициализации блока на DOM-узле, отмеченном как удаленный ([#518](https://github.com/bem/bem-core/issues/518)). - Исправлена ошибка в модуле `i-bem`, из-за которой невозможно было подписаться на событие о выставлении модификатора в значение `false` ([#529](https://github.com/bem/bem-core/issues/529)). - Модуль `jquery` обновлен до версий 2.1.1 и 1.11.1 ([#515](https://github.com/bem/bem-core/issues/515)). ## 2.2.1 - Исправлена ошибка в модуле `jquery__event_type_pointerpressrelease`, из-за которой событие `pointerpress` генерировалось дважды на каждое событие `mousedown` в IE10 ([#505](https://github.com/bem/bem-core/issues/505)). ## 2.2.0 ### Крупные изменения - Добавлен новый модуль `keyboard__codes` ([#431](https://github.com/bem/bem-core/issues/431)). - Класс `BEMContext` добавлен в export-параметры функции `oninit` в базовых шаблонах `i-bem.bemhtml` ([#485](https://github.com/bem/bem-core/pull/485)). - Добавлена возможность декларировать инстанс элемента используя класс блока ([#481](https://github.com/bem/bem-core/issues/481)). - Исправлено поведение метода `isSimple` класса BEMContext в в базовых шаблонах `i-bem.bemhtml` ([#432](https://github.com/bem/bem-core/pull/432)). - Исправлена ошибка в методе `liveUnbindFrom` модуля `BEMDOM` ([#476](https://github.com/bem/bem-core/pull/476)). - Исправлена ошибка в методе `isFocusable` модуля `dom`, возникающая если переданный `domElem` является ссылкой с атрибутом `tabindex`, но без атрибута `href` ([#501](https://github.com/bem/bem-core/issues/501)). - Исправлена ошибка возникающая в процессе декларации БЭМ-блока как модуля, если был подключен модуль `i-bem__dom_elem-instances` ([#479](https://github.com/bem/bem-core/issues/479)). - В модуле `i-bem__dom_init_auto` добавлено временное решение для проблем с производительностью ренедеринга при инициализации блоков в Chrome-браузерах ([#486](https://github.com/bem/bem-core/issues/486)). - Модуль `vow.js` перенесен в `vow.vanilla.js` ([#412](https://github.com/bem/bem-core/issues/412)). ### Также в релиз вошли следующие изменения - Модуль `vow` обновлен до версии 0.4.3 ([#504](https://github.com/bem/bem-core/pull/504)). - Добавлена русская документация на технологию BEMTREE ([#500](https://github.com/bem/bem-core/pull/500)). - Обновлена русская документация на JavaScript-синтаксис BEMHTML ([#471](https://github.com/bem/bem-core/pull/471)). - Добавлен референс на API JavaScript-модулей. См. ветку `v2-jsdoc` ([#478](https://github.com/bem/bem-core/pull/478)). ## 2.1.0 ### Крупные изменения - Исправлена ошибка в `i-bem.js`, из-за которой событие об изменении модификатора происходило до того, как будет вызван обработчик реакции на изменение этого модификатора в `onSetMod` ([#454](https://github.com/bem/bem-core/issues/454)). - Свойства `this.mods` и `this.ctx.mods` базового шаблона `i-bem.bemhtml` теперь используют один и тот же объект ([#441](https://github.com/bem/bem-core/issues/441)). - Модуль [inherit](https://github.com/dfilatov/inherit) обновлен до версии 2.2.1 ([#466](https://github.com/bem/bem-core/issues/466)). - Исправлен порядок тегов секции `head` в шаблоне `page.bemhtml` ([#465](https://github.com/bem/bem-core/pull/465)). ### Также в релиз вошли следующие изменения - В русскую документацию к `i-bem.js` добавлено описание поля `baseMix` ([#461](https://github.com/bem/bem-core/pull/461)). - CDN-хост внешних ресурсов изменен на `yastatic.net` ([#444](https://github.com/bem/bem-core/issues/444)). Все ресурсы все так же доступны с хоста `yandex.st`. Физически `yandex.st` и `yastatic.net` находятся на одних и тех же серверах. Различие только в DNS-записях. - Добавлен базовый BEMHTML-шаблон для команды `bem create` технологии `bemhtml` ([#277](https://github.com/bem/bem-core/issues/277)). - Прекращен автоматический запуск тестов под Node.js 0.8 в [Travis CI](http://travis-ci.com) ([#455](https://github.com/bem/bem-core/issues/455)). - Иконка статуса автосборки Travis [заменена на SVG](http://blog.travis-ci.com/2014-03-20-build-status-badges-support-svg/) :) ## 2.0.0 ### Изменения, ломающие обратную совместимость - Из `i-bem.js` и `i-bem__dom.js` удалены все **deprecated** методы ([#318](https://github.com/bem/bem-core/issues/318)): * `destruct`, используйте `onSetMod js ''`; * `extractParams`, используйте `elemParams`; * `trigger`, используйте `emit`; * `afterCurrentEvent`, используйте модуль `next-tick`; * `channel`, используйте модуль `events__channels`; * `changeThis`, используйте нативный `Function.prototype.bind`. - Из `i-bem.js` убраны события `init` и `destruct`. Вместо них следует использовать события об изменении модификатора (см. «Крупные изменения»). - Блок `ecma` перенесен [в отдельный репозиторий](http://github.com/bem/es5-shims); ES5-shims следует использовать для IE < 9 ([#230](https://github.com/bem/bem-core/issues/230)). - Модуль `vow` обновлен до мажорной версии 0.4.1 ([#350](https://github.com/bem/bem-core/issues/350)). См. [изменения в Vow](https://github.com/dfilatov/vow/blob/0.4.1/CHANGELOG.md). - В `i-bem.bemhtml` добавлена поддержка vow@0.4 ([#385](https://github.com/bem/bem-core/issues/385)). ### Крупные изменения - Добавлена возможность декларировать BEMDOM-блоки как модули [ym](https://github.com/ymaps/modules) ([#382](https://github.com/bem/bem-core/issues/382)). - В `i-bem.js` добавлены события об изменении модификатора ([#357](https://github.com/bem/bem-core/issues/357)). - Добавлена поддержка использования строковых значений в качестве аргумента в методах `BEMDOM.init` ([#419](https://github.com/bem/bem-core/issues/419)) и `BEMDOM.update` ([#420](https://github.com/bem/bem-core/issues/420)). - Методы `i-bem__dom.js` `replace`, `append`, `prepend`, `before`, `after` теперь возвращают новый контекст, а `update` – изменённый ([#410](https://github.com/bem/bem-core/issues/410)). - В `loader` добавлен модификатор `_type_bundle` ([#358](https://github.com/bem/bem-core/issues/358)). - jQuery обновлен до версии 2.1.0. Для IE < 9 — до версии 1.11.0 ([#356](https://github.com/bem/bem-core/issues/356)). ### Также в релиз вошли следующие изменения - Базовые шаблоны в `i-bem.bemhtml` используют конкатенацию строк вместо наполнения внутреннего буфера ([#401](https://github.com/bem/bem-core/issues/401)). - jQuery больше не удаляет себя из глобальной области видимости, если присутствует на странице ([#349](https://github.com/bem/bem-core/issues/349)). - `jquery__event_type_pointerclick.js` перемещен с уровня `touch.blocks` на уровень `common.blocks` ([#393](https://github.com/bem/bem-core/issues/393)). - Модификаторы `i-bem_elem-instances_yes` и `i-bem__dom_elem-instances_yes` приведены к булевому стилю ([#352](https://github.com/bem/bem-core/issues/352)). - Исправлена ошибка в шаблоне блока `page`, возникающая при использовании development-режима BEMHTML ([#417](https://github.com/bem/bem-core/issues/417)). - Для поддержки Android 2.3 внутри `i-bem.js` отказались от использований `Function.prototype.bind` ([#404](https://github.com/bem/bem-core/issues/404)). - Исправлены ошибки в модуле технологии `browser-js+bemhtml` ([#392](https://github.com/bem/bem-core/issues/392)). - NPM-модуль `ym` обновлен до версии [0.0.15](https://github.com/ymaps/modules/releases) ([#414](https://github.com/bem/bem-core/issues/414)). ## 1.2.0 ### Крупные изменения - BEM-блоки инициируют событие `destruct` в процессе удаления ([#370](https://github.com/bem/bem-core/issues/370)). - Исправлены полифилы для `pointerevents` ([#354](https://github.com/bem/bem-core/pull/354)). ### Также в релиз вошли следующие изменения - JSDoc блоков исправлен в соответствии с поддержкой [bem-jsd](github.com/bem/bem-jsd) ([#335](https://github.com/bem/bem-core/issues/335)). - Референс на BEMHTML обновлен для соответствия JavaScript-синтаксису шаблонизатора ([#355](https://github.com/bem/bem-core/pull/355)). - Переход на менеджер зависимостей [bower](http://bower.io) ([#367](https://github.com/bem/bem-core/issues/367)). ## 1.1.0 ### Крупные изменения - Для современных браузеров `jquery__config` подключает jQuery 2.x ([#319](https://github.com/bem/bem-core/issues/319)). - Добавлена возможность использовать произвольный BEMJSON в качестве значения атрибутов в BEMHTML ([#290](https://github.com/bem/bem-core/issues/290)). - Исправлены зависимости в `i-bem__collection` ([#292](https://github.com/bem/bem-core/issues/292)). - Удалены CSS-стили блока `page` из уровня `touch.blocks` ([#306](https://github.com/bem/bem-core/issues/306)). - Исправлена ошибка в BEMHTML-шаблоне блока `page`, приводящая к зацикливанию шаблонизатора в production-режиме ([#309](https://github.com/bem/bem-core/issues/309)). - Исправлена возможная ошибка в `next-tick`, возникающая при вставке скрипта в DOM в IE<9 ([#324](https://github.com/bem/bem-core/issues/324)). - Исправлена ошибка в инициализации плагина `FastClick` в модуле `jquery__event_type_pointerclick` на уровне `touch.blocks` ([#332](https://github.com/bem/bem-core/issues/332)). - Исправлена ошибка в технологии `node.js` в Windows ([#274](https://github.com/bem/bem-core/issues/274)). - Исправлена ошибка в `onElemSetMod` в `i-bem__dom_elem-instances` ([#340](https://github.com/bem/bem-core/issues/340)). - В технологии `bemhtml` используется [bem-xjst](https://github.com/bem/bem-xjst) ([#329](https://github.com/bem/bem-core/issues/329)). ### Также в релиз вошли следующие изменения - Модуль [ym](https://github.com/ymaps/modules) обновлен до версии 0.0.12 ([#326](https://github.com/bem/bem-core/issues/326)). - В ядре локализации `i-bem__i18n` отключен вывод сообщений о неизвестных ключах, если не включен debug-режим ([#285](https://github.com/bem/bem-core/issues/285)). - Инфраструктура сборки тестов и примеров переведена на [bem-pr@v0.5.x](https://github.com/narqo/bem-pr/blob/0.5.3/HISTORY.md) ([#323](https://github.com/bem/bem-core/issues/323)). - Исправлен jsdoc для метода `dropElemCache()` в `i-bem__dom` ([#296](https://github.com/bem/bem-core/issues/296)). - Доработана документация для блока `i-bem.js` на русском языке. - В README проекта добавлен [список поддерживаемых браузеров](https://github.com/bem/bem-core/blob/v1/README.ru.md#%D0%9F%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D0%B2%D0%B0%D0%B5%D0%BC%D1%8B%D0%B5-%D0%B1%D1%80%D0%B0%D1%83%D0%B7%D0%B5%D1%80%D1%8B). ## 1.0.0 ### Крупные изменения - Переход на модульную систему [ym](https://github.com/ymaps/modules). - Из `i-bem`, `i-bem__dom` убраны все deprecated-методы. - `i-bem` больше не зависит от jQuery. `i-bem__dom` продолжает зависеть от jQuery. - BEMHTML-шаблоны можно писать с использованием [JS-синтаксиса](https://gist.github.com/veged/6150760). - Новая технология `bemtree` (на базе [bem-xjst](https://github.com/bem/bem-xjst)) для описания процесса динамического построения БЭМ-дерева. - Новая технология `vanilla.js` для описания JS-реализации модулей, не зависящей от конкретного JavaScript движка. - Новые технологии `browser.js` и `node.js` для описания JS-реализаций модулей (блоков) в соответствующих движках. Для совместимости с существующим кодом считаем, что файлы с расширением `.js` содержат реализацию блоков в технологии `browser.js`. - Система модульного тестирования и примеров для блоков в библиотеке. - Появились полифилы (`jquery__event_type_pointer` и `jquery__event_type_pointerclick` как jQuery-плагины), позволяющие использовать универсальные события для десктопных и тач-интерфейсов. - Плагин для jQuery, позволяющий навешивать обработчик события на нажатие левой кнопки мыши, становится модулем `jquery__pointerclick`. - В `i-bem` и BEMHTML добавлена поддержка простых модификаторов (модификаторов без значений). ### Также в релиз вошли следующие изменения - Все блоки-модули, кроме `i-bem`, избавились от префиксов. - Блок `i-bem__dom` становится модулем `i-bem__dom`. Все BEM.DOM-блоки должны теперь доопределять этот модуль ([пример](https://github.com/bem/bem-core/blob/v1/common.bundles/index/blocks/b-square/b-square.js)). - Метод для декларации блоков (`.decl()`) больше не принимает первым параметром объект с полем `name`. Теперь обязательная форма записи с полем `block`: `BEM.decl({ block: 'b1', modName: 'm', modVal: 'v' }, ...)`. - Вместо метода `afterCurrentEvent` у блоков появился метод `nextTick`, который проверят существование блока в момент исполнения колбэка. `BEM.afterCurrentEvent` теперь **deprecated**. - Вместо `BEM.channel` появился отдельный модуль `channels`. `BEM.channel` теперь **deprecated**. - Метод `changeThis` помечен как **deprecated**. Используйте нативный `bind`. - Метод `del` удален из блока `i-bem`. - Метод `getWindowSize` удален из блока `i-bem__dom`. Используйте `BEMDOM.win.width()` и `BEMDOM.win.height()`. - Добавлен модуль-обертка `jquery`, предоставляющий jQuery. Модуль либо предоставляет jQuery, уже присутствующий на странице, либо сам его загружает (версию 1.10.1). - `$.observable` становится модулем `events` и больше не зависит от jQuery. - `$.inherit` становится модулем `inherit` и больше не зависит от jQuery. - `$.identify` становится модулем `identify` и больше не зависит от jQuery. - `$.throttle` разбивается на два модуля: `functions__throttle` и `functions__debounce`, которые больше не зависят от jQuery. - `$.decodeURI`, `$.decodeURIComponent` переезжают в модуль `querystring__uri` и больше не зависят от jQuery. - `$.cookie` становится модулем `cookie` и больше не зависит от jQuery. - Вместо `$.browser` появился модуль `ua` с аналогичным интерфейсом. - Блок `i-system` разбит на 2 модуля: `idle` и `tick`. - Вместо события `leftclick` следует использовать `pointerclick` (предоставляемый полифилом `jquery__event_type_pointerclick`). - Триггеры на установку модификаторов теперь разделены на две группы: до установки модификатора (`beforeSetMod` и `beforeElemSetMod`) и после (`onSetMod` и `onElemSetMod`). Отмена установки модификатора теперь возможна только из триггеров первой группы. - Использовать конструкцию `{ onSetMod : { js : function() { ... } } }` в качестве конструктора теперь **deprecated**, необходимо использовать `onSetMod: { js : { inited : ... } } }`. - Вместо метода `destruct` в `i-bem` появился зеркальный метод для `onSetMod: { js : { inited : ... } } }` — `{ onSetMod : { js : { '' : ... } } }`. Метод `destruct` теперь **deprecated**. - Метод `exractParams` в `i-bem__dom` теперь **deprecated**, для доступа к параметрам элементов нужно использовать метод `elemParams`. - Метод `trigger` в `i-bem` теперь **deprecated**, нужно использовать `emit`. - Метод `onFirst` в `i-bem` теперь **deprecated**, нужно использовать `once`. - Удалено **deprecated** поле `e.block`, представляющее блок-источник события для BEM-событий. Вместо него следует использовать поле `e.target`. - Для доступа к DOM-элементу блока в обработчике DOM-событий теперь нужно использовать поле `currentTarget`, предоставляемое jQuery. Вместо `e.data.domElem`нужно писать `$(e.currentTarget)`. - В методе `findElem` добавлен параметр, позволяющий находить элемента блока с учетом вложенных блоков. - Добавлена возможность указывать конкретную функцию для отписки от событий в методах `unbindFrom*`. - Добавлен модуль `objects` для работы с JS-объектами (содержит методы `extend`, `isEmpty`, `each`). - Добавлен модуль `functions` для работы с JS-функциями (содержит методы `isFunction`, `noop`). - Добавлен модуль `dom` для хелперов при работе с DOM. - Добавлен модуль `querystring` для работы с урлами. - Добавлен модуль `loader_type_js` для загрузки JS. - Добавлен модуль `vow` для работы с промисами. - Добавлен модуль `next-tick` для полифила `nextTick`, `setImmediate`, `setTimeout(0, ...` и т.п.. - Добавлен модуль `strings__escape`, содержащий методы для эскейпинга XML, HTML и атрибутов. - Модуль `inherit` теперь поддерживает миксины. - В модуле `functions__throttle` добавлен параметр `invokeAsap`, позволяющий отложить первое исполнение. ================================================ FILE: CLA.md ================================================ # Notice to external contributors ## General info Hello! In order for us (YANDEX LLC) to accept patches and other contributions from you, you will have to adopt our Yandex Contributor License Agreement (the “**CLA**”). The current version of the CLA you may find here: 1) https://yandex.ru/legal/cla/?lang=en (in English) and 2) https://yandex.ru/legal/cla/?lang=ru (in Russian). By adopting the CLA, you state the following: * You obviously wish and are willingly licensing your contributions to us for our open source projects under the terms of the CLA, * You has read the terms and conditions of the CLA and agree with them in full, * You are legally able to provide and license your contributions as stated, * We may use your contributions for our open source projects and for any other our project too, * We rely on your assurances concerning the rights of third parties in relation to your contributes. If you agree with these principles, please read and adopt our CLA. By providing us your contributions, you hereby declare that you has already read and adopt our CLA, and we may freely merge your contributions with our corresponding open source project and use it in further in accordance with terms and conditions of the CLA. ## Provide contributions If you have already adopted terms and conditions of the CLA, you are able to provide your contributes. When you submit your pull request, please add the following information into it: ``` I hereby agree to the terms of the CLA available at: [link]. ``` Replace the bracketed text as follows: * `[link]` is the link at the current version of the CLA (you may add here a link https://yandex.ru/legal/cla/?lang=en (in English) or a link https://yandex.ru/legal/cla/?lang=ru (in Russian). It is enough to provide us such notification at once. ## Other questions If you have any questions, please mail us at opensource@yandex-team.ru. ================================================ FILE: CONTRIBUTING.md ================================================ # How to contribute 1. [Create an issue](https://github.com/bem/bem-core/issues/new) with a proper description. 2. Decide which version needs your changes. 3. Create a feature-branch with an issue number and a version (`issues/@v`) based on a version branch. For example, for an issue #42 and a version #1: `git checkout -b issues/42@v1 v1`. If you need changes for several versions, each of them has to have a feature branch. 4. Commit changes accordingly to [CLA](CLA.md) and `push`. Rebase your branch on a corresponding version branch if it's needed. 5. Create a pull-request from your feature branch; or several pull-requests if you changed several versions. 6. Link your pull request with an issue number any way you like. A comment will work perfectly. 7. Wait for your pull request and the issue to be closed ;-) ## Contributors The list of contributors is available at https://github.com/bem/bem-core/graphs/contributors. You may also get it with `git log --pretty=format:"%an <%ae>" | sort -u`. ================================================ FILE: CONTRIBUTING.ru.md ================================================ # Внесение изменений 1. [Создать issue](https://github.com/bem/bem-core/issues/new) с описанием сути изменений. 2. Определить в какую версию необходимо внести изменения. 3. Сделать feature-branch с указанием номера issue и версии (`issues/<номер_issue>@v<номер_версии>`) на основе ветки версии. Например, для issue с номером 42 и версией 1: `git checkout -b issues/42@v1 v1`. Если изменения нужно внести в несколько версий, то для каждой из версий создаётся отдельная ветка. 4. Сделать изменения, закоммитить согласно с [CLA](CLA.md) и сделать push. Если это необходимо, то нужно сделать rebase от базовой ветки версии. 5. Создать pull-request на основе созданной ветки (или несколько pull-request-ов для случая изменений в нескольких версиях). 6. Любым способом связать pull-request и issue (например, c помощью комментария). 7. Ждать закрытия pull-request и issue ;-) ## Контрибьюторы Список контрибьютеров данного проекта доступен по ссылке https://github.com/bem/bem-core/graphs/contributors. Вы так же можете получить его с помощью команды `git log --pretty=format:"%an <%ae>" | sort -u`. ================================================ FILE: LICENSE.txt ================================================ © YANDEX LLC, 2012 The Source Code called `bem-core` available at https://github.com/bem/bem-core is subject to the terms of the Mozilla Public License, v. 2.0 (hereinafter - MPL). The text of MPL is the following: Mozilla Public License, version 2.0 1. Definitions 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means a. that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or b. that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: a. any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or b. any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: a. under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and b. under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: a. for any code that a Contributor has removed from Covered Software; or b. for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or c. under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: a. such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and b. You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. 6. Disclaimer of Warranty Covered Software is provided under this License on an "as is" basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer. 7. Limitation of Liability Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party's negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You. 8. Litigation Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. A copy of the MPL is also available at http://mozilla.org/MPL/2.0/. ================================================ FILE: MIGRATION.md ================================================ # Migration ## 5.0.0 ### ym → ES modules The `ym` module system (`modules.define`/`modules.require`) has been replaced with native ES modules. Before: ```js modules.define('my-block', ['i-bem-dom', 'events'], function(provide, bemDom, events) { provide(bemDom.declBlock(this.name, { /* ... */ })); }); ``` After: ```js import bemDom from 'bem:i-bem-dom'; import events from 'bem:events'; export default bemDom.declBlock('my-block', { /* ... */ }); ``` All `bem:*` imports are resolved at build time by `vite-plugin-bem-levels` to the actual file paths, respecting platform-specific level priorities. ### Module redefinitions Module redefinitions that previously used `modules.define` with a callback receiving the previous module value are now handled via barrel files generated by the Vite plugin. Before (ym redefinition): ```js modules.define('jquery', function(provide, $) { $.event.special.pointerclick = { /* ... */ }; provide($); }); ``` After (side-effect import in barrel): ```js // The barrel file auto-generated by vite-plugin-bem-levels: import $ from '../../common.blocks/jquery/jquery.js'; import '../../common.blocks/jquery/__event/_type/jquery__event_type_pointerclick.js'; export default $; ``` ### jQuery 3 → 4 The `jquery` peer dependency is now `^4.0.0`. Key breaking changes in jQuery 4: - `$.unique()` has been removed. Use `$.uniqueSort()`. - Several deprecated methods have been removed. See [jQuery 4.0 upgrade guide](https://jquery.com/upgrade-guide/4.0/). ### vow → native Promise The `vow` dependency has been removed. All asynchronous code now uses native `Promise`. Before: ```js var vow = require('vow'); var promise = vow.resolve(value); ``` After: ```js const promise = Promise.resolve(value); ``` ### Build system: ENB → Vite The entire ENB toolchain has been replaced with Vite. Before: ```bash ./node_modules/.bin/enb make ``` After: ```bash npm run build # both platforms npm run build:desktop # desktop only npm run build:touch # touch only ``` The Vite config is in `build/vite.config.js`. The custom `vite-plugin-bem-levels` plugin in `build/plugins/` handles BEM level scanning and module resolution. ### Node.js 20+ The minimum supported Node.js version is now 20 (was 8). Update your `.nvmrc` or CI configuration accordingly. ### Linting: jshint/jscs → ESLint 10 Replace any custom `.jshintrc` or `.jscs.json` rules with ESLint flat config (`eslint.config.js`). ### Testing - **Server-side tests**: `mocha`/`chai` have been replaced with `node:test`/`node:assert`. - **Browser tests**: `mocha-phantomjs` has been replaced with Playwright. Run with `npm run test:browser`. - **All tests**: `npm run test:all` runs both server-side and browser tests. ### CI/CD: Travis → GitHub Actions Replace `.travis.yml` with `.github/workflows/ci.yml`. The new CI runs lint, test, test:browser, and build jobs. ### Git hooks: git-hooks → husky Replace `.githooks/` with husky configuration. The `prepare` script in `package.json` sets up husky automatically on `npm install`. ## 4.0.0 ### Changes in the `i-bem` block #### Separate `i-bem-dom` block The `dom` element of the `i-bem` block was moved to a separate `i-bem-dom` block. Before: ```js modules.define('my-dom-block', ['i-bem__dom'], function(provide, BEMDOM) { /* ... */ }); ``` After: ```js modules.define('my-dom-block', ['i-bem-dom'], function(provide, bemDom) { /* ... */ }); ``` The `i-bem` and `i-bem-dom` blocks are no longer classes. They are modules with methods for declaring BEM entities, links to classes of BEM entities, and some additional helpers. These methods are no longer class methods for the corresponding blocks. Issue: [#413](https://github.com/bem/bem-core/issues/413). #### Declaration #### Block declaration Instead of the `decl()` method, use the `declBlock()` method to declare a block. Before: ```js modules.define('my-dom-block', ['i-bem__dom'], function(provide, BEMDOM) { provide(BEMDOM.decl(this.name, { /* ... */ })); }); ``` After: ```js modules.define('my-dom-block', ['i-bem-dom'], function(provide, bemDom) { provide(bemDom.declBlock(this.name, { /* ... */ })); }); ``` #### Modifier declaration Instead of the static `decl()` method, use the static `declMod()` method to declare a modifier. Before: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.decl({ modName : 'my-mod', modVal : 'my-val' }, { /* ... */ })); }); ``` After: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.declMod({ modName : 'my-mod', modVal : 'myVal' }, { /* ... */ })); }); ``` #### Boolean modifier declaration Before: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.decl({ modName : 'my-mod', modVal : 'true' }, { /* ... */ })); }); ``` After: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.declMod({ modName : 'my-mod' }, { /* ... */ })); }); ``` Issue: [#1374](https://github.com/bem/bem-core/issues/1374). #### Declaration for a modifier with any value Before: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.decl({ modName : 'my-mod' }, { /* ... */ })); }); ``` After: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.declMod({ modName : 'my-mod', modVal : '*' }, { /* ... */ })); }); ``` Issue: [#1376](https://github.com/bem/bem-core/pull/1376). #### Block redefinition Instead of the `decl()` method for a block class, use the `declBlock()` method for the `i-bem-dom` module. Before: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.decl({ /* ... */ })); }); ``` After: ```js modules.define('my-dom-block', ['i-bem-dom'], function(provide, bemDom, MyDomBlock) { provide(bemDom.declBlock(MyDomBlock, { /* ... */ })); }); ``` #### Inherited block declaration Before: ```js modules.define('my-dom-block', ['i-bem__dom', 'my-base-dom-block'], function(provide, BEMDOM, MyBaseDomBlock) { provide(BEMDOM.decl({ block : this.name, baseBlock : MyBaseDomBlock }, { /* ... */ })); }); ``` After: ```js modules.define('my-dom-block', ['i-bem-dom', 'my-base-dom-block'], function(provide, bemDom, MyBaseDomBlock) { provide(bemDom.declBlock(this.name, MyBaseDomBlock, { /* ... */ })); }); ``` #### Mix declaration The `declMix` method has been renamed to `declMixin`. This clarifies the concept of [mixes of multiple BEM entities on a single DOM node](https://en.bem.info/methodology/key-concepts/#Mix) as opposed to JS-level mixins. Before: ```js modules.define('my-mix-block', ['i-bem__dom'], function(provide, BEMDOM) { provide(BEMDOM.declMix(this.name, { /* ... */ })); }); ``` After: ```js modules.define('my-mixin-block', ['i-bem-dom'], function(provide, bemDom) { provide(bemDom.declMixin({ /* ... */ })); }); ``` #### Mixing a mixin Before: ```js modules.define('my-dom-block', ['i-bem__dom', 'my-mix-1', 'my-mix-2'], function(provide, BEMDOM) { provide(BEMDOM.decl({ block : this.name, baseMix : ['my-mix-1', 'my-mix-2']}, { /* ... */ })); }); ``` After: ```js modules.define('my-dom-block', ['i-bem-dom', 'my-mixin-1', 'my-mixin-2'], function(provide, bemDom, MyMixin1, MyMixin2) { provide(bemDom.declBlock(this.name, [MyMixin1, MyMixin2], { /* ... */ })); }); ``` #### Triggers for changing modifiers When declaring a specific modifier (for example, `_my-mod_my-val`), it wasn't possible to declare the behavior for deleting this modifier. We had to make two declarations. Before: ```js // modules.define('my-dom-block', function(provide, MyDomBlock) { MyDomBlock.decl({ onSetMod : { 'my-mod' : { '' : function() { /* ... */ } // declaration for deleting the _my-mod_my-val modifier } } }); provide(MyDomBlock.decl({ modName : 'my-mod', modVal : 'my-val' }, { /* ... */ })); }); ``` After: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.declMod({ modName : 'my-mod', modVal : 'my-val' }, { onSetMod : { 'mod1' : { '' : function() { /* ... */ } // declaration for deleting the _my-mod_my-val modifier } } })); }); ``` Issue: [#1025](https://github.com/bem/bem-core/issues/1025). Shorthand syntax is now available for declaring behaviors for changing modifiers. Before: ```js onSetMod : { 'my-mod' : { '*' : function(modName, modVal, prevModVal) { if(prevModVal === 'my-val') { /* ... */ // declaration for changing _my-mod_my-val to any other value } } } } ``` After: ```js onSetMod : { 'my-mod' : { '~my-val' : function() { /* ... */ } // declaration for changing the my-mod value from my-val to any other value } } } ``` Before: ```js onSetMod : { 'my-mod' : { '*' : function(modName, modVal) { if(modVal !== 'my-val') { /* ... */ // declaration for changing my-mod to any value other than my-val } } } } ``` After: ```js onSetMod : { 'my-mod' : { '!my-val' : function() { /* ... */ } // declaration for changing my-mod to any value other than my-val } } } ``` Issue: [#1072](https://github.com/bem/bem-core/issues/1072). #### Lazy initialization The functionality of the `live` field has been divided into two parts: the `lazyInit` field and the `onInit()` method. Before: ```js modules.define('my-dom-block', ['i-bem__dom'], function(provide, BEMDOM) { provide(BEMDOM.decl(this.name, { /* ... */ }, { live : true })); }); ``` After: ```js modules.define('my-dom-block', ['i-bem-dom'], function(provide, bemDom) { provide(bemDom.declBlock(this.name, { /* ... */ }, { lazyInit : true })); }); ``` Before: ```js modules.define('my-dom-block', ['i-bem__dom'], function(provide, BEMDOM) { provide(BEMDOM.decl(this.name, { /* ... */ }, { live : function() { /* ... */ } })); }); ``` After: ```js modules.define('my-dom-block', ['i-bem-dom'], function(provide, bemDom) { provide(bemDom.declBlock(this.name, { /* ... */ }, { lazyInit : true, onInit : function() { /* ... */ } })); }); ``` Before: ```js modules.define('my-dom-block', ['i-bem__dom'], function(provide, BEMDOM) { provide(BEMDOM.decl(this.name, { /* ... */ }, { live : function() { /* ... */ return false; } })); }); ``` After: ```js modules.define('my-dom-block', ['i-bem-dom'], function(provide, bemDom) { provide(bemDom.declBlock(this.name, { /* ... */ }, { onInit : function() { /* ... */ } })); }); ``` Before: ```js { block : 'b1', js : { live : false } } ``` After: ```js { block : 'b1', js : { lazyInit : false } } ``` Issue: [#877](https://github.com/bem/bem-core/issues/877). #### Instances for elements Deleted the `elem-instances` element of the `i-bem` block and the `elem-instances` modifier of the `dom` element in the `i-bem` block. Now the corresponding functionality is incorporated into `i-bem` and `i-bem-dom`. Before: ```js modules.define('my-dom-block__my-elem', ['i-bem__dom'], function(provide, BEMDOM) { provide(BEMDOM.decl({ block : 'my-dom-block', elem : 'my-elem' }, { /* ... */ })); }); ``` After: ```js modules.define('my-dom-block__my-elem', ['i-bem-dom'], function(provide, bemDom) { provide(bemDom.declElem('my-dom-block', 'my-elem', { /* ... */ })); }); ``` Now the `_elem(elemName)` method of the block instance (previously `elem(elemName)`) returns an instance of the element's class, instead of a jQuery object with all the elements named `elemName`. To get a collection of instances of the element's class, use the `_elems()` method. Now the caches for elements with JS implementation found with `_elem()` and `_elems()` are invalidated automatically when the DOM is modified. Issue: [#1352](https://github.com/bem/bem-core/issues/1352). Note: When this methods are used for elements without JS implementation you still need to use `_dropElemCache()` in cases of dynamically DOM update. Note: don't forget to switch on support for elements instances in template engine. For `bem-xjst` please refer to https://github.com/bem/bem-xjst/blob/master/docs/en/3-api.md#support-js-instances-for-elements-bem-core-v4 or for `BH` see https://github.com/bem/bh#jselem. ##### Ways to work with elements Before: ```js this.setMod(this.elem('my-elem'), 'my-mod', 'my-val'); ``` After: ```js this._elem('my-elem').setMod('my-mod', 'my-val'); ``` The same is true for the methods `getMod()`, `hasMod()`, `toggleMod()`, and `delMod()`. ##### Deleted methods and fields The following methods were deleted from the block API: `elemify()`, `elemParams()`, and the `onElemSetMod` field. The corresponding functionality is provided in instances of elements. Also see the changes for [search methods](#Search-methods). Issue: [#581](https://github.com/bem/bem-core/issues/581). #### Search methods Renamed the following methods: - `findBlockInside()` to `findChildBlock()` - `findBlocksInside()` to `findChildBlocks()` - `findBlockOutside()` to `findParentBlock()` - `findBlocksOutside()` to `findParentBlocks()` - `findBlockOn()` to `findMixedBlock()` - `findBlocksOn()` to `findMixedBlocks()` The optional first parameter about the element has been removed from these methods. Added the following methods: `findChildElem()`, `findChildElems()`, `findParentElem()`, `findParentElems()`, `findMixedElem()`, `findMixedElems()`. Before: ```js this.findBlockInside(this.elem('my-elem'), 'my-block-2'); ``` After: ```js this.findChildElem('my-elem').findChildBlock(MyBlock2); ``` Deleted the following methods: `findElem()`, `closestElem()`. Use the `findChildElem()` and `findParentElem()` elements, instead. The methods `findChildBlocks()`, `findParentBlocks()`, `findMixedBlocks()`, `findChildElems()`, `findParentElems()`, and `findMixedElems()` return [collections of BEM entities](#Collections). The `findChildElem()` and `findChildElems()` methods (unlike the previous equivalent `findElem`) don't search on their own DOM nodes of the instance. Before: ```js this.findElem('my-elem'); ``` After: ```js this.findChildElems('my-elem').concat(this.findMixedElems('my-elem')); ``` However, consider whether you really need both searches. In most cases, you can just use either `this.findChildElems('my-elem')` or `this.findMixedElems('my-elem')`. ##### Checking for nesting In place of the deleted `containsDomElem()` method, use the `containsEntity()` method. Before: ```js this.containsDomElem(someElem); ``` After: ```js this.containsEntity(someElem); ``` #### Collections The functionality of the `collection` element of the `i-bem` block is no longer optional. All methods that return an array of BEM entities now return collections. Before: ```js this.findBlocksInside('my-block-2')[0].setMod('my-mod', 'my-val'); ``` After: ```js this.findChildBlocks(MyBlock2).get(0).setMod('my-mod', 'my-val'); ``` Before: ```js this.findBlocksInside('my-block-2').forEach(function(myBlock2) { return myBlock2.setMod('my-mod', 'my-val'); }); ``` After: ```js this.findChildBlocks(MyBlock2).setMod('my-mod', 'my-val'); ``` Issue: [#582](https://github.com/bem/bem-core/issues/582). #### Events The events API has been simplified. Deleted the following block instance methods: `on()`, `un()`, `once()`, `bindTo()`, `unbindFrom()`, `bindToDoc()`, `bindToWin()`, `unbindFromDoc()`, `unbindFromWin()`, and class methods: `liveBindTo()`, `liveUnbindFrom()`, `on()`, `un()`, `once()`, `liveInitOnBlockEvent()`, `liveInitOnBlockInsideEvent()`. They have been replaced with the new `_domEvents()` and `_events()` methods, which return an instance of the events manager class with the `on()`, `un()` and `once()` methods. ##### DOM events on instances Before: ```js BEMDOM.decl('my-block', { onSetMod : { 'js' : { 'inited' : function() { this.bindTo('click', this._onClick); } } } }); ``` After: ```js bemDom.declBlock('my-block', { onSetMod : { 'js' : { 'inited' : function() { this._domEvents().on('click', this._onClick); } } } }); ``` Before: ```js BEMDOM.decl('my-block', { onSetMod : { 'js' : { 'inited' : function() { this.bindToDoc('click', this._onDocClick); } } } }); ``` After: ```js bemDom.declBlock('my-block', { onSetMod : { 'js' : { 'inited' : function() { this._domEvents(bemDom.doc).on('click', this._onDocClick); } } } }); ``` Before: ```js BEMDOM.decl('my-block', { onSetMod : { 'js' : { 'inited' : function() { this.bindToWin('resize', this._onWinResize); } } } }); ``` After: ```js bemDom.declBlock('my-block', { onSetMod : { 'js' : { 'inited' : function() { this._domEvents(bemDom.win).on('resize', this._onWinResize); } } } }); ``` ##### Link to instance If an event was fired on BEM instance the event object will contain a link to an instance: ```js this._domEvents('my-elem').on('click', function(e) { e.bemTarget // refers to `my-elem` instance }); ``` ##### BEM events on instances Before: ```js BEMDOM.decl('my-block', { onSetMod : { 'js' : { 'inited' : function() { this.findBlockOutside('my-block-2').on('my-event', this._onMyBlock2MyEvent, this); }, '' : function() { this.findBlockOutside('my-block-2').un('my-event', this._onMyBlock2MyEvent, this); } } } }); ``` After: ```js bemDom.declBlock('my-block', { onSetMod : { 'js' : { 'inited' : function() { this._events(this.findParentBlock('my-block-2')).on('my-event', this._onMyBlock2MyEvent); } } } }); ``` Note that unsubscribing from events is now automatic when the instance is destroyed. ##### Delegated DOM events Before: ```js BEMDOM.decl('my-block', { /* ... */ }, { live : function() { this.liveBindTo('click', this.prototype._onClick); } }); ``` After: ```js bemDom.declBlock('my-block', { /* ... */ }, { onInit : function() { this._domEvents().on('click', this.prototype._onClick); } }); ``` Before: ```js BEMDOM.decl('my-block', { /* ... */ }, { live : function() { this.liveBindTo('my-elem', 'click', this.prototype._onMyElemClick); } }); ``` After: ```js bemDom.declBlock('my-block', { /* ... */ }, { onInit : function() { this._domEvents('my-elem').on('click', this.prototype._onMyElemClick); } }); ``` ##### Delegated BEM events Before: ```js BEMDOM.decl('my-block', { /* ... */ }, { live : function() { this.liveInitOnBlockInsideEvent('my-event', 'my-block-2', this.prototype._onMyBlock2MyEvent); } }); ``` After: ```js bemDom.declBlock('my-block', { /* ... */ }, { onInit : function() { this._events(MyBlock2).on('my-event', this.prototype._onMyBlock2MyEvent); } }); ``` Note that the parameter with the event handler function is now required. Before: ```js modules.define('my-block', ['i-bem__dom', 'my-block-2'], function(provide, BEMDOM) { provide(BEMDOM.decl(this.name, { /* ... */ }, { live : function() { this.liveInitOnBlockInsideEvent('my-event', 'my-block-2'); } })); }); ``` After: ```js modules.define('my-block', ['i-bem-dom', 'my-block-2', 'functions'], function(provide, bemDom, MyBlock2, functions) { provide(bemDom.declBlock(this.name, { /* ... */ }, { onInit : function() { this._events(MyBlock2).on('my-event', functions.noop); } })); }); ``` Before: ```js BEMDOM.decl('my-block', { onSetMod : { 'js' : { 'inited' : function() { MyBlock2.on(this.domElem, 'my-event', this._onMyBlock2MyEvent, this); }, '' : function() { MyBlock2.un(this.domElem, 'my-event', this._onMyBlock2MyEvent, this); } } } }); ``` After: ```js bemDom.declBlock('my-block', { onSetMod : { 'js' : { 'inited' : function() { this._events(MyBlock2).on('my-event', this._onMyBlock2MyEvent); } } } }); ``` Note that unsubscribing from events is now automatic when the instance is destroyed. #### External code accessing BEM blocks ##### Getting an instance of a BEM block Now the `bem()` method of a jQuery object accepts a BEM class instead of a string. Before: ```js modules.require(['jquery', 'i-bem__dom'], function($, BEMDOM) { var myBlock = $('.my-block').bem('my-block'); }); ``` After: ```js modules.require(['jquery', 'my-block'], function($, MyBlock) { var myBlock = $('.my-block').bem(MyBlock); }); ``` ##### Subscribing to BEM events from external code Before: ```js modules.require(['jquery', 'i-bem__dom'], function($, BEMDOM) { $('.my-block').bem('my-block').on('my-event', function() { /* ... */ }); }); ``` After: ```js modules.require(['jquery', 'my-block', 'events__observable'], function($, MyBlock, observable) { observable($('.my-block').bem(MyBlock)) .on('my-event', function() { /* ... */ }); }); ``` In addition, you must add `{ block : 'events', elem : 'observable', mods : { type : 'bem-dom' } }` to the dependency. Issue: [#394](https://github.com/bem/bem-core/issues/394). #### Names of protected methods begin with `_` Renamed the protected methods: - `emit()` to `_emit()` - `elem()` to `_elem()` - `dropElemCache()` to `_dropElemCache()` - `buildClass()` to `_buildClassName()` - `buildSelector()` to `_buildSelector()` - `getDefaultParams()` to `_getDefaultParams()` Issues: [#586](https://github.com/bem/bem-core/issues/586) and [#1359](https://github.com/bem/bem-core/issues/1359). #### Deleted methods Deleted the `getMods()` method. ### Changes in the `querystring` block The `querystring__uri` element is now the `uri` block. The `querystring` block is now the `uri__querystring` element. Issue: [#967](https://github.com/bem/bem-core/issues/967). ### Changes in the `page` block The `page__css` element does not support `ie` field. Use the `page__conditional-comment` element instead. Before: ``` { block : 'page', head : [ { elem : 'css', url : 'my-css.css', ie : false }, { elem : 'css', url : 'my-css', ie : true } ], content : 'Page content' } ``` After: ``` { block : 'page', head : [ { elem : 'conditional-comment', condition : '! IE', content : { elem : 'css', url : 'my-css.css' } }, { elem : 'conditional-comment', condition : '> IE 8', content : { elem : 'css', url : 'my-css.ie.css' } } // and so on for needed IE versions ], content : 'Page content' } ``` Issue: [#379](https://github.com/bem/bem-core/issues/379). ## 3.0.0 To migrate to version 3.0.0, review the [history of changes](https://en.bem.info/libs/bem-core/v3/changelog/#300). ## 2.0.0 To migrate to version 2.0.0, review the [history of changes](https://en.bem.info/libs/bem-core/v2/changelog/#200). ## 1.0.0 For version 1.0.0, migrating requires switching from using [bem-bl](https://github.com/bem/bem-bl/) to using [bem-core](https://github.com/bem/bem-core/). ### Modules The entire code is now written in terms of the modular system https://github.com/ymaps/modules. All dependencies must be explicitly stated in the code. Minimize or eliminate use of global variables, if possible. Example: ```js modules.define( 'my-module', // module name ['module-from-library', 'my-another-module'], // module dependencies function(provide, moduleFromLibrary, myAnotherModule) { // module declaration, called when all dependencies are resolved // module representation provide({ myModuleMethod : function() {} }); }); ``` TODO: Add information about the build process (usage of special technologies for JS and instructions for custom builders). ### jQuery and jQuery plugins jQuery is represented by a `jquery` wrapper module that uses the global jQuery object if it already exists on the page, or loads it otherwise. jQuery is now used only for operations directly related to the DOM (searching for elements, binding listeners to events, setting and getting attribute values, and so on). All other operations have corresponding modules that provide the same functionality without depending on jQuery: * The `objects` module for operating on objects (with the `extend`, `isEmpty`, and `each` methods). * The `functions` module for operating on functions (with the `isFunction` and `noop` methods). In addition, all the jQuery plugins that aren't directly related to jQuery (`$.observable`, `$.inherit`, `$.cookie`, `$.identify`, `$.throttle`) are now modules: * The `events` module replaces `$.observable` for working with events. It provides the "classes" `EventsEmitter` and `Event`. * The `inherit` module instead of `$.inherit` for working with "classes" and inheritance. * The `cookie` module instead of `$.cookie`. * The `identify` module instead of `$.identify`. * The `functions__throttle` and `functions__debounce` modules instead of `$.throttle` and `$.debounce`. Before: ```js // block code $.throttle() // block code ``` After: ```js module.define('my-module', ['functions__throttle'], function(provide, throttle) { // module code throttle() // module code }); ``` ### BEM.DOM blocks #### Declaration Instead of a declaration via BEM.DOM.decl, you need to extend the `i-bem__dom` module. Before: ```js BEM.DOM.decl('block', /* ... */); ``` After: ```js modules.define('i-bem__dom', function(provide, BEMDOM) { BEMDOM.decl('block', /* ... */); provide(BEMDOM); }); ``` #### Constructor You must use full notation for the handler for setting the `js` modifier to `inited`. Before: ```js onSetMod : { js : function() { // constructor code } } ``` After: ```js onSetMod : { 'js' : { 'inited' : function() { // constructor code } } } ``` #### Desctructor Instead of the `destruct` method, you need to use the handler for setting the `js` modifier to an empty value (remove the modifier). You no longer need to call `__base` in order to run the base destructor defined in `i-bem__dom` on blocks. Before: ```js destruct : function() { this.__base.apply(this, arguments); // destructor code } ``` After: ```js onSetMod : { js : { '' : function() { // destructor code } } } ``` #### `changeThis` method Instead of the `changeThis` method, use either the corresponding parameter, or the native `bind` method if there isn't a parameter. Before: ```js // block code obj.on('event', this.changeThis(this._method)); // block code ``` After: ```js obj.on('event', this._method.bind(this)); // or better obj.on('event', this._method, this); ``` #### `afterCurrentEvent` method Instead of the `afterCurrentEvent` method, use the `nextTick` method, which guarantees that the block still exists during the callback (if the block has already been destroyed by this time, the callback isn't executed). Before: ```js BEM.DOM.decl('block', { method : function() { this.afterCurrentEvent(function() { /* ... */ }) } }); ``` After: ```js modules.define('i-bem__dom', function(provide, BEMDOM) { BEMDOM.decl('block', { method : function() { this.nextTick(function() { /* ... */ }); } }); }); ``` #### `findElem` method The context for finding an element is no longer set as a string. Instead, pass a jQuery object. Before: ```js var nestedElem = this.findElem('parent-elem', 'nested-elem'); ``` After: ```js var nestedElem = this.findElem(this.findElem('parent-elem'), 'nested-elem'), oneMoreElem = this.findElem(this.elem('another-elem'), 'nested-elem'); ``` #### `liveBindTo` method The `liveBindTo` method no longer supports the `elemName` field for passing the element name. Use the `elem` field instead. #### Access to a DOM element in an event handler A DOM element that had an event handler bound to it is now accessed as `$(e.currentTarget)` instead of `e.data.domElem`. Before: ```js onClick : function(e) { e.data.domElem.attr(/* ... */); } ``` After: ```js onClick : function(e) { $(e.currentTarget).attr(/* ... */); } ``` #### Channels Channels are no longer an integral part of BEM. Now they are separate `events__channels` modules. Before: ```js BEM.DOM.decl('block', { method : function() { BEM.channel('channel-name').on(/* ... */); } }); ``` After: ```js modules.define('i-bem__dom', ['events__channels'], function(provide, channels, BEMDOM) { BEMDOM.decl('block', { method : function() { channels('channel-name').on(/* ... */); } }); }); ``` #### The `i-system` block and the `sys` channel for the `tick`, `idle`, and `wakeup` events This block and channel no longer exist. They have been replaced with separate modules: `tick` with the "tick" event, and `idle` with the "idle" and "wakeup" events. Before: ```js BEM.DOM.decl('block', { method : function() { BEM.channel('sys').on('tick', /* ... */); } }); ``` After: ```js modules.define('i-bem__dom', ['tick'], function(provide, tick, BEMDOM) { BEMDOM.decl('block', { method : function() { tick.on('tick', /* ... */); } }); }); ``` Before: ```js BEM.DOM.decl('block', { method : function() { BEM.channel('sys').on('wakeup', /* ... */); } }); ``` After: ```js modules.define('i-bem__dom', ['idle'], function(provide, idle, BEMDOM) { BEMDOM.decl('block', { method : function() { idle.on('wakeup', /* ... */); } }); }); ``` ### BEM blocks BEM blocks that were used as storage for some methods but that didn't use the BEM methodology in any way can now be written as modules. Before: ```js BEM.decl('i-router', { route : function() { /* ... */ } }); ``` After: ```js modules.define('router', function(provide) { provide({ route : function() { /* ... */ } }); }); ``` If for some reason you need BEM blocks (not BEM.DOM blocks), you can declare them by extending the `i-bem` module. Before: ```js BEM.decl('my-block', { /* ... */ }); ``` After: ```js modules.define('i-bem', function(provide, BEM) { BEM.decl('my-block', { /* ... */ }); provide(BEM); }); ``` #### Refactoring using the `b-spin` block example Before: ```js BEM.DOM.decl('b-spin', { onSetMod : { 'js' : function() { this._size = this.getMod('size') || /[\d]+/.exec(this.getMod('theme'))[0]; this._bgProp = 'background-position'; this._posPrefix = '0 -'; if (this.elem('icon').css('background-position-y')) { /* In IE, you can't get the background-position property. You can only get background-position-y, so use this workaround */ this._bgProp = 'background-position-y'; this._posPrefix = '-'; } this._curFrame = 0; this.hasMod('progress') && this.channel('sys').on('tick', this._onTick, this); }, 'progress' : { 'yes' : function() { this.channel('sys').on('tick', this._onTick, this); }, '' : function() { this.channel('sys').un('tick', this._onTick, this); } } }, _onTick: function(){ var y = ++this._curFrame * this._size; (y >= this._size * 36) && (this._curFrame = y = 0); this.elem('icon').css(this._bgProp, this._posPrefix + y +'px'); }, destruct : function() { this.channel('sys').un('tick', this._onTick, this); this.__base.apply(this, arguments); } }); ``` After: ```js modules.define( 'i-bem__dom', ['tick'], function(provide, tick, BEMDOM) { var FRAME_COUNT = 36; BEMDOM.decl('b-spin', { onSetMod : { 'js' : { 'inited' : function() { // constructor var hasBackgroundPositionY = !!this.elem('icon').css('background-position-y')); /* In IE we can't get the background-position property, only background-position-y */ this._bgProp = hasBackgroundPositionY? 'background-position-y' : 'background-position'; this._posPrefix = hasBackgroundPositionY? '-' : '0 -'; this._curFrame = 0; this._size = Number(this.getMod('size') || /[\d]+/.exec(this.getMod('theme'))[0]); this.hasMod('progress') && this._bindToTick(); }, '' : function() { // destructor this._unbindFromTick(); } }, 'progress' : { 'true' : function() { this._bindToTick(); }, '' : function() { this._unbindFromTick(); } } }, _bindToTick : function() { tick.on('tick', this._onTick, this); }, _unbindFromTick : function() { tick.un('tick', this._onTick, this); }, _onTick : function() { var offset; this._curFrame++ >= FRAME_COUNT? offset = this._curFrame * this._size : this._curFrame = offset = 0; this.elem('icon').css(this._bgProp, this._posPrefix + offset + 'px'); } }); provide(BEMDOM); }); ``` ================================================ FILE: MIGRATION.ru.md ================================================ # Миграция ## 5.0.0 ### ym → ES-модули Модульная система `ym` (`modules.define`/`modules.require`) заменена на нативные ES-модули. Было: ```js modules.define('my-block', ['i-bem-dom', 'events'], function(provide, bemDom, events) { provide(bemDom.declBlock(this.name, { /* ... */ })); }); ``` Стало: ```js import bemDom from 'bem:i-bem-dom'; import events from 'bem:events'; export default bemDom.declBlock('my-block', { /* ... */ }); ``` Все `bem:*` импорты разрешаются на этапе сборки плагином `vite-plugin-bem-levels` в реальные пути файлов с учётом приоритетов уровней для конкретной платформы. ### Переопределения модулей Переопределения модулей, которые ранее использовали `modules.define` с колбэком, получающим предыдущее значение модуля, теперь обрабатываются через barrel-файлы, генерируемые Vite-плагином. Было (переопределение ym): ```js modules.define('jquery', function(provide, $) { $.event.special.pointerclick = { /* ... */ }; provide($); }); ``` Стало (side-effect импорт в barrel-файле): ```js // Barrel-файл автоматически сгенерирован vite-plugin-bem-levels: import $ from '../../common.blocks/jquery/jquery.js'; import '../../common.blocks/jquery/__event/_type/jquery__event_type_pointerclick.js'; export default $; ``` ### jQuery 3 → 4 Peer-зависимость `jquery` теперь `^4.0.0`. Основные несовместимые изменения в jQuery 4: - `$.unique()` удалён. Используйте `$.uniqueSort()`. - Ряд устаревших методов удалён. См. [руководство по обновлению jQuery 4.0](https://jquery.com/upgrade-guide/4.0/). ### vow → нативный Promise Зависимость `vow` удалена. Весь асинхронный код теперь использует нативный `Promise`. Было: ```js var vow = require('vow'); var promise = vow.resolve(value); ``` Стало: ```js const promise = Promise.resolve(value); ``` ### Система сборки: ENB → Vite Весь инструментарий ENB заменён на Vite. Было: ```bash ./node_modules/.bin/enb make ``` Стало: ```bash npm run build # обе платформы npm run build:desktop # только desktop npm run build:touch # только touch ``` Конфигурация Vite находится в `build/vite.config.js`. Кастомный плагин `vite-plugin-bem-levels` в `build/plugins/` обеспечивает сканирование BEM-уровней и разрешение модулей. ### Node.js 20+ Минимальная поддерживаемая версия Node.js — 20 (было 8). Обновите `.nvmrc` или конфигурацию CI. ### Линтинг: jshint/jscs → ESLint 10 Замените кастомные `.jshintrc` или `.jscs.json` правила на flat-конфигурацию ESLint (`eslint.config.js`). ### Тестирование - **Серверные тесты**: `mocha`/`chai` заменены на `node:test`/`node:assert`. - **Браузерные тесты**: `mocha-phantomjs` заменён на Playwright. Запуск: `npm run test:browser`. - **Все тесты**: `npm run test:all` запускает серверные и браузерные тесты. ### CI/CD: Travis → GitHub Actions Замените `.travis.yml` на `.github/workflows/ci.yml`. Новый CI запускает задачи lint, test, test:browser и build. ### Git-хуки: git-hooks → husky Замените `.githooks/` на конфигурацию husky. Скрипт `prepare` в `package.json` автоматически настраивает husky при `npm install`. ## 4.0.0 ### Изменения в блоке `i-bem` #### Отдельный блок `i-bem-dom` Элемент `dom` блока `i-bem` был перенесён в отдельный блок `i-bem-dom`. Было: ```js modules.define('my-dom-block', ['i-bem__dom'], function(provide, BEMDOM) { /* ... */ }); ``` Стало: ```js modules.define('my-dom-block', ['i-bem-dom'], function(provide, bemDom) { /* ... */ }); ``` Блоки `i-bem` и `i-bem-dom` больше не являются классами, представляя собой модули с методами для декларации БЭМ-сущностей, ссылками на классы БЭМ-сущностей и некоторыми дополнительными хелперами. Эти методы больше не являются методами класса для соответсвующих блоков. Задача: [#413](https://github.com/bem/bem-core/issues/413). #### Декларация #### Декларация блока Для декларации блока, вместо метода `decl()`, следует использовать метод `declBlock()`. Было: ```js modules.define('my-dom-block', ['i-bem__dom'], function(provide, BEMDOM) { provide(BEMDOM.decl(this.name, { /* ... */ })); }); ``` Стало: ```js modules.define('my-dom-block', ['i-bem-dom'], function(provide, bemDom) { provide(bemDom.declBlock(this.name, { /* ... */ })); }); ``` #### Декларация модификатора Для декларации модификатора, вместо статического метода `decl()`, следует использовать статический метод `declMod()`. Было: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.decl({ modName : 'my-mod', modVal : 'my-val' }, { /* ... */ })); }); ``` Стало: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.declMod({ modName : 'my-mod', modVal : 'myVal' }, { /* ... */ })); }); ``` #### Декларация булевого модификатора Было: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.decl({ modName : 'my-mod', modVal : 'true' }, { /* ... */ })); }); ``` Стало: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.declMod({ modName : 'my-mod' }, { /* ... */ })); }); ``` Задача: [#1374](https://github.com/bem/bem-core/issues/1374). #### Декларация для модификатора с любым значением Было: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.decl({ modName : 'my-mod' }, { /* ... */ })); }); ``` Стало: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.declMod({ modName : 'my-mod', modVal : '*' }, { /* ... */ })); }); ``` Задача: [#1376](https://github.com/bem/bem-core/pull/1376). #### Доопределение блока Вместо метода `decl()` класса блока следует использовать метод `declBlock()` модуля `i-bem-dom`. Было: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.decl({ /* ... */ })); }); ``` Стало: ```js modules.define('my-dom-block', ['i-bem-dom'], function(provide, bemDom, MyDomBlock) { provide(bemDom.declBlock(MyDomBlock, { /* ... */ })); }); ``` #### Декларация наследуемого блока Было: ```js modules.define('my-dom-block', ['i-bem__dom', 'my-base-dom-block'], function(provide, BEMDOM, MyBaseDomBlock) { provide(BEMDOM.decl({ block : this.name, baseBlock : MyBaseDomBlock }, { /* ... */ })); }); ``` Стало: ```js modules.define('my-dom-block', ['i-bem-dom', 'my-base-dom-block'], function(provide, bemDom, MyBaseDomBlock) { provide(bemDom.declBlock(this.name, MyBaseDomBlock, { /* ... */ })); }); ``` #### Декларация миксина Метод `declMix` переименован в `declMixin`, чтобы отделить понятие [миксов нескольких БЭМ-сущностей на одном DOM-узле](https://ru.bem.info/methodology/key-concepts/#Микс) от миксинов на уровне JS. Было: ```js modules.define('my-mix-block', ['i-bem__dom'], function(provide, BEMDOM) { provide(BEMDOM.declMix(this.name, { /* ... */ })); }); ``` Стало: ```js modules.define('my-mixin-block', ['i-bem-dom'], function(provide, bemDom) { provide(bemDom.declMixin({ /* ... */ })); }); ``` #### Примешивание миксина Было: ```js modules.define('my-dom-block', ['i-bem__dom', 'my-mix-1', 'my-mix-2'], function(provide, BEMDOM) { provide(BEMDOM.decl({ block : this.name, baseMix : ['my-mix-1', 'my-mix-2']}, { /* ... */ })); }); ``` Стало: ```js modules.define('my-dom-block', ['i-bem-dom', 'my-mixin-1', 'my-mixin-2'], function(provide, bemDom, MyMixin1, MyMixin2) { provide(bemDom.declBlock(this.name, [MyMixin1, MyMixin2], { /* ... */ })); }); ``` #### Триггеры для изменения модификаторов При декларации определённого модификатора (например, `_my-mod_my-val`) невозможно было задекларировать поведение на удаление этого модификатора. Приходилось делать две декларации. Было: ```js // my-dom-block_my-mod_my-val.js modules.define('my-dom-block', function(provide, MyDomBlock) { MyDomBlock.decl({ onSetMod : { 'my-mod' : { '' : function() { /* ... */ } // декларация для удаления модификатора _my-mod_my-val } } }); provide(MyDomBlock.decl({ modName : 'my-mod', modVal : 'my-val' }, { /* ... */ })); }); ``` Стало: ```js modules.define('my-dom-block', function(provide, MyDomBlock) { provide(MyDomBlock.declMod({ modName : 'my-mod', modVal : 'my-val' }, { onSetMod : { 'mod1' : { '' : function() { /* ... */ } // декларация для удаления модификатора _my-mod_my-val } } })); }); ``` Задача: [#1025](https://github.com/bem/bem-core/issues/1025). Появился сокращённый синтаксис для декларации поведения на изменение модификатора. Было: ```js onSetMod : { 'my-mod' : { '*' : function(modName, modVal, prevModVal) { if(prevModVal === 'my-val') { /* ... */ // декларация для изменения _my-mod_my-val в любое другое значение } } } } ``` Стало: ```js onSetMod : { 'my-mod' : { '~my-val' : function() { /* ... */ } // декларация для изменения значения my-mod из my-val в любое другое значение } } } ``` Было: ```js onSetMod : { 'my-mod' : { '*' : function(modName, modVal) { if(modVal !== 'my-val') { /* ... */ // декларация для изменения my-mod в любое значение, кроме my-val } } } } ``` Стало: ```js onSetMod : { 'my-mod' : { '!my-val' : function() { /* ... */ } // декларация для изменения my-mod в любое значение, кроме my-val } } } ``` Задача: [#1072](https://github.com/bem/bem-core/issues/1072). #### Ленивая инициализация Функциональность поля `live` была разделена на две части: поле `lazyInit` и метод `onInit()`. Было: ```js modules.define('my-dom-block', ['i-bem__dom'], function(provide, BEMDOM) { provide(BEMDOM.decl(this.name, { /* ... */ }, { live : true })); }); ``` Стало: ```js modules.define('my-dom-block', ['i-bem-dom'], function(provide, bemDom) { provide(bemDom.declBlock(this.name, { /* ... */ }, { lazyInit : true })); }); ``` Было: ```js modules.define('my-dom-block', ['i-bem__dom'], function(provide, BEMDOM) { provide(BEMDOM.decl(this.name, { /* ... */ }, { live : function() { /* ... */ } })); }); ``` Стало: ```js modules.define('my-dom-block', ['i-bem-dom'], function(provide, bemDom) { provide(bemDom.declBlock(this.name, { /* ... */ }, { lazyInit : true, onInit : function() { /* ... */ } })); }); ``` Было: ```js modules.define('my-dom-block', ['i-bem__dom'], function(provide, BEMDOM) { provide(BEMDOM.decl(this.name, { /* ... */ }, { live : function() { /* ... */ return false; } })); }); ``` Стало: ```js modules.define('my-dom-block', ['i-bem-dom'], function(provide, bemDom) { provide(bemDom.declBlock(this.name, { /* ... */ }, { onInit : function() { /* ... */ } })); }); ``` Было: ```js { block : 'b1', js : { live : false } } ``` Стало: ```js { block : 'b1', js : { lazyInit : false } } ``` Задача: [#877](https://github.com/bem/bem-core/issues/877). #### Экземпляры для элементов Удалены элемент `elem-instances` блока `i-bem` и модификатор `elem-instances` элемента `dom` блока `i-bem`. Теперь соответствующая функциональность является частью `i-bem` и `i-bem-dom`. Было: ```js modules.define('my-dom-block__my-elem', ['i-bem__dom'], function(provide, BEMDOM) { provide(BEMDOM.decl({ block : 'my-dom-block', elem : 'my-elem' }, { /* ... */ })); }); ``` Стало: ```js modules.define('my-dom-block__my-elem', ['i-bem-dom'], function(provide, bemDom) { provide(bemDom.declElem('my-dom-block', 'my-elem', { /* ... */ })); }); ``` Теперь метод `_elem(elemName)` экземпляра блока (бывший `elem(elemName)`) возвращает не jQuery-объект со всеми элементами с именем `elemName`, а экземпляр класса элемента. Для того, чтобы получить коллекцию экземпляров класса элемента, используйте метод `_elems()`. Теперь кэш для элементов с JS-реализацией найденных через `_elem()` и `_elems()` инвалидируется автоматически при DOM модификациях. Задача: [#1352](https://github.com/bem/bem-core/issues/1352). Когда эти методы используются для элементов без JS-реализации необходимо использовать `_dropElemCache()` при динамическом обновлении DOM. Не забудьте включить поддержку экземплятор для элементов в шаблонизаторе. Опция [elemJsInstances](https://github.com/bem/bem-xjst/blob/master/docs/ru/3-api.md#%D0%9F%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%BA%D0%B0-js-%D1%8D%D0%BA%D0%B7%D0%B5%D0%BC%D0%BF%D0%BB%D1%8F%D1%80%D0%BE%D0%B2-%D0%B4%D0%BB%D1%8F-%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2-bem-core-v4) для `bem-xjst` или [jsElem](https://github.com/bem/bh#jselem) для `BH`. ##### Cпособы взаимодействия с элементами Было: ```js this.setMod(this.elem('my-elem'), 'my-mod', 'my-val'); ``` Стало: ```js this._elem('my-elem').setMod('my-mod', 'my-val'); ``` Аналогично для методов `getMod()`, `hasMod()`, `toggleMod()`, `delMod()`. ##### Удалённые методы и поля Из API блока удалены методы: `elemify()`, `elemParams()` и поле `onElemSetMod`. Соответствующая им функциональность выражается через экземпляры элементов . См. также изменения про [методы поиска](#Методы-поиска). Задача: [#581](https://github.com/bem/bem-core/issues/581). #### Методы поиска Переименованы следующие методы: - `findBlockInside()` в `findChildBlock()` - `findBlocksInside()` в `findChildBlocks()` - `findBlockOutside()` в `findParentBlock()` - `findBlocksOutside()` в `findParentBlocks()` - `findBlockOn()` в `findMixedBlock()` - `findBlocksOn()` в `findMixedBlocks()` Из этих методов удален опциональный первый параметр про элемент. Добавлены методы: `findChildElem()`, `findChildElems()`, `findParentElem()`, `findParentElems()`, `findMixedElem()`, `findMixedElems()`. Было: ```js this.findBlockInside(this.elem('my-elem'), 'my-block-2'); ``` Стало: ```js this.findChildElem('my-elem').findChildBlock(MyBlock2); ``` Удалены методы: `findElem()`, `closestElem()`, вместо них следует использовать методы `findChildElem()` и `findParentElem()`, соответсвенно. Методы `findChildBlocks()`, `findParentBlocks()`, `findMixedBlocks()`, `findChildElems()`, `findParentElems()`, `findMixedElems()` возвращают [коллекции БЭМ-сущностей](#Коллекции). Методы `findChildElem()` и `findChildElems()` (в отличие от предыдущего аналога `findElem`) не выполняют поиск на собственных DOM-узлах экземпляра. Было: ```js this.findElem('my-elem'); ``` Стало: ```js this.findChildElems('my-elem').concat(this.findMixedElems('my-elem')); ``` Но рекомендуется обратить внимание, действительно ли необходимы оба поиска: в большинстве случаев достаточно использовать или `this.findChildElems('my-elem')` или `this.findMixedElems('my-elem')`. ##### Проверка вложенности Вместо удаленного метода `containsDomElem()`, следует использовать метод `containsEntity()`. Было: ```js this.containsDomElem(someElem); ``` Стало: ```js this.containsEntity(someElem); ``` #### Коллекции Функциональность элемента `collection` блока `i-bem` перестала быть опциональной. Все методы возвращавшие массив БЭМ-сущностей, теперь возвращают коллекции. Было: ```js this.findBlocksInside('my-block-2')[0].setMod('my-mod', 'my-val'); ``` Стало: ```js this.findChildBlocks(MyBlock2).get(0).setMod('my-mod', 'my-val'); ``` Было: ```js this.findBlocksInside('my-block-2').forEach(function(myBlock2) { return myBlock2.setMod('my-mod', 'my-val'); }); ``` Стало: ```js this.findChildBlocks(MyBlock2).setMod('my-mod', 'my-val'); ``` Задача: [#582](https://github.com/bem/bem-core/issues/582). #### События API работы с событиями значильно упрощено. Удалены методы экземпляра блока: `on()`, `un()`, `once()`, `bindTo()`, `unbindFrom()`, `bindToDoc()`, `bindToWin()`, `unbindFromDoc()`, `unbindFromWin()` и методы класса: `liveBindTo()`, `liveUnbindFrom()`, `on()`, `un()`, `once()`, `liveInitOnBlockEvent()`, `liveInitOnBlockInsideEvent()`. Вместо них добавлены методы `_domEvents()` и `_events()`, возвращающие экземпляр класса менеджера событий, с методами `on()`, `un()` и `once()`; ##### DOM-события на экземплярах Было: ```js BEMDOM.decl('my-block', { onSetMod : { 'js' : { 'inited' : function() { this.bindTo('click', this._onClick); } } } }); ``` Стало: ```js bemDom.declBlock('my-block', { onSetMod : { 'js' : { 'inited' : function() { this._domEvents().on('click', this._onClick); } } } }); ``` Было: ```js BEMDOM.decl('my-block', { onSetMod : { 'js' : { 'inited' : function() { this.bindToDoc('click', this._onDocClick); } } } }); ``` Стало: ```js bemDom.declBlock('my-block', { onSetMod : { 'js' : { 'inited' : function() { this._domEvents(bemDom.doc).on('click', this._onDocClick); } } } }); ``` Было: ```js BEMDOM.decl('my-block', { onSetMod : { 'js' : { 'inited' : function() { this.bindToWin('resize', this._onWinResize); } } } }); ``` Стало: ```js bemDom.declBlock('my-block', { onSetMod : { 'js' : { 'inited' : function() { this._domEvents(bemDom.win).on('resize', this._onWinResize); } } } }); ``` ##### Ссылка на экземпляр Если событие произошло на БЭМ-экземпляре, в объект события будет добавлено поле, ссылающееся на экземпляр: ```js this._domEvents('my-elem').on('click', function(e) { e.bemTarget // ссылается на экземпляр `my-elem` }); ``` ##### БЭМ-события на экземплярах Было: ```js BEMDOM.decl('my-block', { onSetMod : { 'js' : { 'inited' : function() { this.findBlockOutside('my-block-2').on('my-event', this._onMyBlock2MyEvent, this); }, '' : function() { this.findBlockOutside('my-block-2').un('my-event', this._onMyBlock2MyEvent, this); } } } }); ``` Стало: ```js bemDom.declBlock('my-block', { onSetMod : { 'js' : { 'inited' : function() { this._events(this.findParentBlock('my-block-2')).on('my-event', this._onMyBlock2MyEvent); } } } }); ``` Следует обратить внимание, что теперь, отписка от событий происходит автоматически во время уничтожения экземпляра. ##### Делегированные DOM-события Было: ```js BEMDOM.decl('my-block', { /* ... */ }, { live : function() { this.liveBindTo('click', this.prototype._onClick); } }); ``` Стало: ```js bemDom.declBlock('my-block', { /* ... */ }, { onInit : function() { this._domEvents().on('click', this.prototype._onClick); } }); ``` Было: ```js BEMDOM.decl('my-block', { /* ... */ }, { live : function() { this.liveBindTo('my-elem', 'click', this.prototype._onMyElemClick); } }); ``` Стало: ```js bemDom.declBlock('my-block', { /* ... */ }, { onInit : function() { this._domEvents('my-elem').on('click', this.prototype._onMyElemClick); } }); ``` ##### Делегированные БЭМ-события Было: ```js BEMDOM.decl('my-block', { /* ... */ }, { live : function() { this.liveInitOnBlockInsideEvent('my-event', 'my-block-2', this.prototype._onMyBlock2MyEvent); } }); ``` Стало: ```js bemDom.declBlock('my-block', { /* ... */ }, { onInit : function() { this._events(MyBlock2).on('my-event', this.prototype._onMyBlock2MyEvent); } }); ``` Следует обратить внимание, что параметр с функцией обработчиком события теперь обязательный. Было: ```js modules.define('my-block', ['i-bem__dom', 'my-block-2'], function(provide, BEMDOM) { provide(BEMDOM.decl(this.name, { /* ... */ }, { live : function() { this.liveInitOnBlockInsideEvent('my-event', 'my-block-2'); } })); }); ``` Стало: ```js modules.define('my-block', ['i-bem-dom', 'my-block-2', 'functions'], function(provide, bemDom, MyBlock2, functions) { provide(bemDom.declBlock(this.name, { /* ... */ }, { onInit : function() { this._events(MyBlock2).on('my-event', functions.noop); } })); }); ``` Было: ```js BEMDOM.decl('my-block', { onSetMod : { 'js' : { 'inited' : function() { MyBlock2.on(this.domElem, 'my-event', this._onMyBlock2MyEvent, this); }, '' : function() { MyBlock2.un(this.domElem, 'my-event', this._onMyBlock2MyEvent, this); } } } }); ``` Стало: ```js bemDom.declBlock('my-block', { onSetMod : { 'js' : { 'inited' : function() { this._events(MyBlock2).on('my-event', this._onMyBlock2MyEvent); } } } }); ``` Следует обратить внимание, что теперь, отписка от событий происходит автоматически во время уничтожения экземпляра. #### Взаимодействие стороннего кода с БЭМ-блоками ##### Получение экземпляра БЭМ-блока Теперь, метод jQuery-объекта `bem()` принимает БЭМ-класс, вместо строки. Было: ```js modules.require(['jquery', 'i-bem__dom'], function($, BEMDOM) { var myBlock = $('.my-block').bem('my-block'); }); ``` Стало: ```js modules.require(['jquery', 'my-block'], function($, MyBlock) { var myBlock = $('.my-block').bem(MyBlock); }); ``` ##### Подписка на БЭМ-события из стороннего кода Было: ```js modules.require(['jquery', 'i-bem__dom'], function($, BEMDOM) { $('.my-block').bem('my-block').on('my-event', function() { /* ... */ }); }); ``` Стало: ```js modules.require(['jquery', 'my-block', 'events__observable'], function($, MyBlock, observable) { observable($('.my-block').bem(MyBlock)) .on('my-event', function() { /* ... */ }); }); ``` При этом в зависимости нужно добавить `{ block : 'events', elem : 'observable', mods : { type : 'bem-dom' } }`. Задача: [#394](https://github.com/bem/bem-core/issues/394). #### Имена protected-методов начинаются с `_` Переименованы protected-методы: - `emit()` в `_emit()` - `elem()` в `_elem()` - `dropElemCache()` в `_dropElemCache()` - `buildClass()` в `_buildClassName()` - `buildSelector()` в `_buildSelector()` - `getDefaultParams()` в `_getDefaultParams()` Задачи: [#586](https://github.com/bem/bem-core/issues/586), [#1359](https://github.com/bem/bem-core/issues/1359). #### Удалённые методы Удалён метод `getMods()`. ### Изменения в блоке `querystring` Элемент `querystring__uri` стал блоком `uri`. Блок `querystring` стал элементом `uri__querystring`. Задача: [#967](https://github.com/bem/bem-core/issues/967). ### Изменения в блоке `page` Элемент `page__css` больше не поддреживает поле `ie`. Используйте элемент `page__conditional-comment`. Было: ``` { block : 'page', head : [ { elem : 'css', url : 'my-css.css', ie : false }, { elem : 'css', url : 'my-css', ie : true } ], content : 'Page content' } ``` Стало: ``` { block : 'page', head : [ { elem : 'conditional-comment', condition : '! IE', content : { elem : 'css', url : 'my-css.css' } }, { elem : 'conditional-comment', condition : '> IE 8', content : { elem : 'css', url : 'my-css.ie.css' } } // и т.д. для других нужных версий IE ], content : 'Page content' } ``` Задача: [#379](https://github.com/bem/bem-core/issues/379). ## 3.0.0 Для миграции на версию 3.0.0 достаточно ознакомиться с [историей изменений](https://ru.bem.info/libs/bem-core/v3/changelog/#300). ## 2.0.0 Для миграции на версию 2.0.0 достаточно ознакомиться с [историей изменений](https://ru.bem.info/libs/bem-core/v2/changelog/#200). ## 1.0.0 Для версии 1.0.0 миграция подразумевается с использования [bem-bl](https://github.com/bem/bem-bl/) на использование [bem-core](https://github.com/bem/bem-core/). ### Модули Весь код теперь пишется в терминах модульной системы https://github.com/ymaps/modules. Все зависимости должны явно указываться в коде, обращения к глобальным объектам необходимо минимизировать, а, по возможности, и полностью исключить. Пример: ```js modules.define( 'my-module', // имя модуля ['module-from-library', 'my-another-module'], // зависимости модуля function(provide, moduleFromLibrary, myAnotherModule) { // декларация модуля, вызывается когда все зависимости "разрезолвлены" // предоставление модуля provide({ myModuleMethod : function() {} }); }); ``` TODO: дописать про изменение сборки (использование специальных технологий для js и как быть с кастомными сборщиками) ### jQuery и jQuery-плагины jQuery представлен модулем-оберткой `jquery`, который использует глобальный объект jQuery, в случае если он уже присутствует на странице, в противном случае загружая его самостоятельно. jQuery теперь используется только для операций, связанных непосредственно с DOM (поиск элементов, подписка на события, установка/получение атрибутов элементов, и т.д.). Для всех остальных операций написаны соответствующие модули, предоставляющие аналогичный функционал, но, при этом, не зависящие от jQuery: * модуль `objects` для работы с объектами (с методами `extend`, `isEmpty`, `each`) * модуль `functions` для работы с функциями (с методами `isFunction` и `noop`) Также, все jQuery-плагины, не связанные непосредственно с jQuery (`$.observable`, `$.inherit`, `$.cookie`, `$.identify`, `$.throttle`) стали модулями: * модуль `events` вместо `$.observable` для работы с событиями, предоставляющий "классы" `EventsEmitter` и `Event` * модуль `inherit` вместо `$.inherit` для работы с "классами" и наследованием * модуль `cookie` вместо `$.cookie` * модуль `identify` вместо `$.identify` * модули `functions__throttle`, `functions__debounce` вместо `$.throttle` и `$.debounce`, соответственно Было: ```js // код блока $.throttle() // код блока ``` Стало: ```js module.define('my-module', ['functions__throttle'], function(provide, throttle) { // код модуля throttle() // код модуля }); ``` ### BEM.DOM-блоки #### Декларация Вместо декларации через BEM.DOM.decl необходимо доопределять модуль `i-bem__dom`. Было: ```js BEM.DOM.decl('block', /* ... */); ``` Стало: ```js modules.define('i-bem__dom', function(provide, BEMDOM) { BEMDOM.decl('block', /* ... */); provide(BEMDOM); }); ``` #### Конструктор Необходимо использовать полную нотацию для обработчика установки модификатора `js` в значение `inited`. Было: ```js onSetMod : { js : function() { // код конструктора } } ``` Стало: ```js onSetMod : { 'js' : { 'inited' : function() { // код конструктора } } } ``` #### Деструктор Вместо метода `destruct` необходимо использовать обработчик установки модификатора `js` в пустое значение (удаление модификатора). Вызывать `__base` для того, чтобы у блоков работал базовый деструктор, определенный в `i-bem__dom`, больше не нужно. Было: ```js destruct : function() { this.__base.apply(this, arguments); // код деструктора } ``` Стало: ```js onSetMod : { js : { '' : function() { // код деструктора } } } ``` #### Метод `changeThis` Вместо метода `changeThis` необходимо использовать либо соответствующий параметр, либо нативный метод `bind`, если такой параметр отсутствует. Было: ```js // код блока obj.on('event', this.changeThis(this._method)); // код блока ``` Стало: ```js obj.on('event', this._method.bind(this)); // или лучше obj.on('event', this._method, this); ``` #### Метод `afterCurrentEvent` Вместо метода `afterCurrentEvent` необходимо использовать метод `nextTick`, который гарантирует, что блок еще существует в момент исполнения callback'а (если блок уже уничтожен к этому моменту, то callback не исполняется). Было: ```js BEM.DOM.decl('block', { method : function() { this.afterCurrentEvent(function() { /* ... */ }); } }); ``` Стало: ```js modules.define('i-bem__dom', function(provide, BEMDOM) { BEMDOM.decl('block', { method : function() { this.nextTick(function() { /* ... */ }); } }); }); ``` #### Метод `findElem` Контекст для поиска элемента больше не задается строкой, вместо нее следует передавать jQuery-объект. Было: ```js var nestedElem = this.findElem('parent-elem', 'nested-elem'); ``` Стало: ```js var nestedElem = this.findElem(this.findElem('parent-elem'), 'nested-elem'), oneMoreElem = this.findElem(this.elem('another-elem'), 'nested-elem'); ``` #### Метод `liveBindTo` Метод `liveBindTo` больше не поддерживает поле `elemName` для передачи имени элемента. Вместо него следует использовать поле `elem`. #### Доступ до DOM-элемента в обработчике события DOM-элемент, к которому был подвешен обработчик события теперь доступен как `$(e.currentTarget)`вместо `e.data.domElem`. Было: ```js onClick : function(e) { e.data.domElem.attr(/* ... */); } ``` Стало: ```js onClick : function(e) { $(e.currentTarget).attr(/* ... */); } ``` #### Каналы (channels) Каналы больше не являются встроенными в BEM, теперь они являются самостоятельным модулем `events__channels`. Было: ```js BEM.DOM.decl('block', { method : function() { BEM.channel('channel-name').on(/* ... */); } }); ``` Стало: ```js modules.define('i-bem__dom', ['events__channels'], function(provide, channels, BEMDOM) { BEMDOM.decl('block', { method : function() { channels('channel-name').on(/* ... */); } }); }); ``` #### Блок `i-system` и канал `sys` событий `tick`, `idle`, `wakeup` Этот блок и канал перестали существовать, вместо них появились отдельные модули: `tick` с событием tick и `idle` с событиями idle и wakeup. Было: ```js BEM.DOM.decl('block', { method : function() { BEM.channel('sys').on('tick', /* ... */); } }); ``` Стало: ```js modules.define('i-bem__dom', ['tick'], function(provide, tick, BEMDOM) { BEMDOM.decl('block', { method : function() { tick.on('tick', /* ... */); } }); }); ``` Было: ```js BEM.DOM.decl('block', { method : function() { BEM.channel('sys').on('wakeup', /* ... */); } }); ``` Стало: ```js modules.define('i-bem__dom', ['idle'], function(provide, idle, BEMDOM) { BEMDOM.decl('block', { method : function() { idle.on('wakeup', /* ... */); } }); }); ``` ### BEM-блоки Те BEM-блоки, которые использовались как хранилище для каких-то методов, при этом никак не использующие BEM-методологию, теперь могут быть написаны как модули. Было: ```js BEM.decl('i-router', { route : function() { /* ... */ } }); ``` Стало: ```js modules.define('router', function(provide) { provide({ route : function() { /* ... */ } }); }); ``` Если же, по каким-то причинам, нужны именно BEM-блоки (не BEM.DOM-блоки), то их можно объявлять, доопределяя модуль `i-bem`. Было: ```js BEM.decl('my-block', { /* ... */ }); ``` Стало: ```js modules.define('i-bem', function(provide, BEM) { BEM.decl('my-block', { /* ... */ }); provide(BEM); }); ``` #### Рефакторинг на примере блока `b-spin` Было: ```js BEM.DOM.decl('b-spin', { onSetMod : { 'js' : function() { this._size = this.getMod('size') || /[\d]+/.exec(this.getMod('theme'))[0]; this._bgProp = 'background-position'; this._posPrefix = '0 -'; if (this.elem('icon').css('background-position-y')) { /* В IE нельзя получить свойство background-position, только background-position-y, поэтому костыляем */ this._bgProp = 'background-position-y'; this._posPrefix = '-'; } this._curFrame = 0; this.hasMod('progress') && this.channel('sys').on('tick', this._onTick, this); }, 'progress' : { 'yes' : function() { this.channel('sys').on('tick', this._onTick, this); }, '' : function() { this.channel('sys').un('tick', this._onTick, this); } } }, _onTick: function(){ var y = ++this._curFrame * this._size; (y >= this._size * 36) && (this._curFrame = y = 0); this.elem('icon').css(this._bgProp, this._posPrefix + y +'px'); }, destruct : function() { this.channel('sys').un('tick', this._onTick, this); this.__base.apply(this, arguments); } }); ``` Стало: ```js modules.define( 'i-bem__dom', ['tick'], function(provide, tick, BEMDOM) { var FRAME_COUNT = 36; BEMDOM.decl('b-spin', { onSetMod : { 'js' : { 'inited' : function() { // конструктор var hasBackgroundPositionY = !!this.elem('icon').css('background-position-y')); /* В IE нельзя получить свойство background-position, только background-position-y */ this._bgProp = hasBackgroundPositionY? 'background-position-y' : 'background-position'; this._posPrefix = hasBackgroundPositionY? '-' : '0 -'; this._curFrame = 0; this._size = Number(this.getMod('size') || /[\d]+/.exec(this.getMod('theme'))[0]); this.hasMod('progress') && this._bindToTick(); }, '' : function() { // деструктор this._unbindFromTick(); } }, 'progress' : { 'true' : function() { this._bindToTick(); }, '' : function() { this._unbindFromTick(); } } }, _bindToTick : function() { tick.on('tick', this._onTick, this); }, _unbindFromTick : function() { tick.un('tick', this._onTick, this); }, _onTick : function() { var offset; this._curFrame++ >= FRAME_COUNT? offset = this._curFrame * this._size : this._curFrame = offset = 0; this.elem('icon').css(this._bgProp, this._posPrefix + offset + 'px'); } }); provide(BEMDOM); }); ``` ================================================ FILE: PLAN.md ================================================ # Plan: Modernization of bem-core Dependencies ## Current State Analysis ### Runtime Environment | Component | Current | Target | Status | |-----------|---------|--------|--------| | Node.js | 8 | 24 LTS (Krypton, v24.13.1) | 16 major versions behind | | npm | 5-6 (lockfile v1) | 11+ (lockfile v3) | Needs regeneration | ### Dependencies — Current vs Latest #### Production (`dependencies`) | Package | Current | Latest | Last Published | Status | |---------|---------|--------|----------------|--------| | `ym` | ^0.1.2 | 0.1.2 | ancient | **Abandoned**. BEM module system. No updates for years. | #### Dev Dependencies (`devDependencies`) **Build System (ENB) — ALL ABANDONED:** | Package | Current | Latest | Last Published | Status | |---------|---------|--------|----------------|--------| | `enb` | ^1.2.0 | 1.5.1 | 2017-11 | **Abandoned** | | `enb-bem-techs` | ^2.2.2 | 2.2.2 | 2017-12 | **Abandoned** | | `enb-magic-factory` | ^0.6.0 | 0.6.0 | 2018-02 | **Abandoned** | | `enb-magic-platform` | 0.7.0 | 0.7.0 | 2016-04 | **Abandoned** | | `enb-bemxjst` | ^8.10.2 | 8.10.6 | ~2018 | **Abandoned** | | `enb-bemxjst-6x` | ^6.5.3 | — | — | **Abandoned** | | `enb-bemxjst-7x` | ^7.3.1 | — | — | **Abandoned** | | `enb-bemxjst-i18n` | 1.0.0-beta3 | — | — | **Abandoned** | | `enb-bh` | ^1.2.1 | — | — | **Abandoned** | | `enb-bh-i18n` | 1.0.0-beta2 | — | — | **Abandoned** | | `enb-borschik` | ^2.8.0 | — | — | **Abandoned** | | `enb-css` | ^1.2.2 | 1.2.2 | — | **Abandoned** | | `enb-js` | ^1.1.1 | 1.1.1 | — | **Abandoned** | | `enb-bem-docs` | 0.14.1 | 0.15.0 | 2019-02 | **Abandoned** | | `enb-bem-examples` | ^1.0.2 | 1.0.2 | 2016-04 | **Abandoned** | | `enb-bem-specs` | ^0.11.0 | 0.11.0 | 2016-12 | **Abandoned** | | `enb-bem-tmpl-specs` | ^1.3.3 | 1.3.3 | 2018-03 | **Abandoned** | | `enb-bem-i18n` | ^1.1.1 | — | — | **Abandoned** | **Linting — ABANDONED/OUTDATED:** | Package | Current | Latest | Last Published | Status | |---------|---------|--------|----------------|--------| | `jscs` | ^2.11.0 | 3.0.7 | 2016-07 | **Abandoned** (merged into ESLint) | | `jscs-bem` | ^0.2.0 | — | — | **Abandoned** | | `jshint` | ^2.9.1 | 2.13.6 | maintained | Functional but superseded by ESLint | | `jshint-groups` | ^0.8.0 | — | — | **Abandoned** | **Testing — PARTIALLY ABANDONED:** | Package | Current | Latest | Last Published | Status | |---------|---------|--------|----------------|--------| | `mocha` | ^3.3.0 | 11.7.5 | active | 8 major versions behind | | `mocha-phantomjs` | ^4.1.0 | 4.1.0 | 2016-06 | **Abandoned** (PhantomJS is dead) | | `chai` | ^3.2.0 | 6.2.2 | active | 3 major versions behind; v5+ is ESM-only | | `chai-as-promised` | ^5.1.0 | — | — | Outdated | | `istanbul` | ^0.4.3 | 0.4.5 | 2016-08 | **Abandoned** (replaced by nyc → c8) | **BEM Tools:** | Package | Current | Latest | Last Published | Status | |---------|---------|--------|----------------|--------| | `bem-naming` | ^1.0.1 | 1.0.1 | — | **Abandoned** | | `bem-walk` | 1.0.0-alpha1 | 1.0.0-1 | — | **Never left alpha** | **Other:** | Package | Current | Latest | Last Published | Status | |---------|---------|--------|----------------|--------| | `borschik` | ^1.5.3 | 3.0.0 | 2021-02 | Unmaintained | | `bower` | ^1.7.9 | 1.8.14 | 2022-03 | **Deprecated** since 2017 | | `git-hooks` | ^1.0.2 | 1.1.10 | — | Superseded by husky | | `gitbook-api` | ^3.0.2 | — | — | **Abandoned** | | `jsdoc` | ^3.5.5 | 4.0.5 | active | 1 major version behind | | `vow` | ^0.4.17 | 0.4.20 | 2019-07 | **Abandoned** (native Promises exist) | ### Configuration Files to Replace | File | Purpose | Modern Replacement | |------|---------|-------------------| | `.jshintrc` | JSHint config | `eslint.config.js` (ESLint flat config) | | `.jscs.json` | JSCS style config | `eslint.config.js` (ESLint flat config) | | `.jshint-groups.js` | JSHint groups config | `eslint.config.js` (ESLint flat config) | | `.bowerrc` | Bower directory config | Remove (drop Bower) | | `bower.json` | Bower package manifest | Remove (drop Bower) | | `.travis.yml` | Travis CI config | `.github/workflows/ci.yml` (GitHub Actions) | | `.enb/` (entire dir) | ENB build config | New build system config | | `.githooks/pre-commit/lint` | Pre-commit hook | `.husky/pre-commit` | ### CI/CD | Component | Current | Target | |-----------|---------|--------| | CI system | Travis CI | GitHub Actions | | Coverage | Istanbul + Coveralls | c8 + Coveralls (or Codecov) | | Node.js in CI | 8 | 24 | --- ## Implementation Plan ### Phase 0: Preparation 1. Create feature branch `claude/update-dependencies-fWO1e` 2. Verify project builds and tests in current state (baseline) ### Phase 1: Node.js & npm Modernization 1. Add `.nvmrc` with `24` 2. Add `engines` field to `package.json`: `"node": ">=24"`, `"npm": ">=11"` 3. Delete `package-lock.json` (will regenerate with lockfile v3) ### Phase 2: Remove Abandoned/Deprecated Tools 1. **Remove Bower**: delete `bower.json`, `.bowerrc`, remove `bower i` from scripts 2. **Remove JSCS**: delete `.jscs.json`, uninstall `jscs`, `jscs-bem` 3. **Remove JSHint**: delete `.jshintrc`, `.jshint-groups.js`, uninstall `jshint`, `jshint-groups` 4. **Remove Istanbul**: uninstall `istanbul` 5. **Remove mocha-phantomjs**: uninstall `mocha-phantomjs` 6. **Remove gitbook-api**: uninstall `gitbook-api` 7. **Remove git-hooks**: delete `.githooks/` directory, uninstall `git-hooks` ### Phase 3: Linting — Migrate to ESLint 10 1. Install `eslint@^10.0.1` 2. Create `eslint.config.js` (flat config, required for ESLint 10) migrating rules from: - `.jshintrc` rules → ESLint equivalents - `.jscs.json` BEM preset rules → ESLint equivalents - `.jshint-groups.js` file-group-specific overrides → ESLint flat config overrides 3. Support file extensions: `.js`, `.bemtree`, `.bemhtml` 4. Update `package.json` `lint` script: `"lint": "eslint ."` 5. Delete old config files: `.jshintrc`, `.jscs.json`, `.jshint-groups.js` ### Phase 4: Testing Modernization 1. **Upgrade Mocha**: `mocha@^11.7.5` 2. **Upgrade Chai**: `chai@^6.2.2` (ESM-only — requires `"type": "module"` or `.mjs` for test files using it) - Alternative: stay on `chai@^4.x` (last CJS version) if ESM migration is too invasive 3. **Replace Istanbul with c8**: install `c8@^10.1.3` 4. **Replace mocha-phantomjs with Playwright**: install `playwright@^1.58.2` and `@playwright/test` - Browser spec tests (`.spec.js` files using `modules.define`) need adaptation for Playwright 5. Update test scripts in `package.json` ### Phase 5: Build System — ENB → Vite 6 + BEM Levels Plugin > **This is the highest-risk, highest-effort phase.** The entire ENB ecosystem (17+ packages) is abandoned. > **Chosen approach: Vite 6** with custom `vite-plugin-bem-levels` for BEM level resolution + barrel file generation, plus dedicated plugins for BEMHTML/BH template compilation and i18n. #### What ENB Currently Does (to be replaced) | ENB Function | Packages | Vite Replacement | |---|---|---| | BEM level scanning & file resolution | `enb-bem-techs` | `vite-plugin-bem-levels` (custom) | | JS bundling with `ym` module system | `enb-js` + `ym` | Vite native ES modules + barrel files | | Module redefinition (`modules.define` chains) | `ym` runtime | Platform barrel files (auto-generated) | | CSS concatenation | `enb-css` | Vite native CSS handling | | BEMHTML template compilation | `enb-bemxjst`, `enb-bemxjst-6x`, `enb-bemxjst-7x` | `vite-plugin-bemhtml` (custom, wraps `bem-xjst`) | | BH template compilation | `enb-bh` | `vite-plugin-bh` (custom, wraps `bh`) | | i18n keysets processing | `enb-bem-i18n`, `enb-bemxjst-i18n`, `enb-bh-i18n` | `vite-plugin-bem-i18n` (custom) | | Minification (borschik) | `enb-borschik`, `borschik` | Vite built-in (esbuild/terser for JS, lightningcss for CSS) | | HTML from BEMJSON | `enb-bemxjst`, `enb-bh` | Build script using compiled templates | | Examples/tests/specs building | `enb-bem-examples`, `enb-bem-specs`, `enb-bem-docs`, `enb-magic-*` | Vite dev server + Playwright | #### Phase 5.0: Preparation & Coexistence > ENB and Vite will coexist during migration. ENB stays functional until Vite fully replaces it. 1. Install Vite 6 and core dependencies: ``` npm i -D vite@^6 @anthropic-ai/vite-plugin-bem-levels ``` (Initially `vite-plugin-bem-levels` will live in `build/plugins/` as a local module) 2. Create build directory structure: ``` build/ ├── plugins/ │ ├── vite-plugin-bem-levels.js — BEM level resolution + barrel generation │ ├── vite-plugin-bemhtml.js — BEMHTML compilation │ ├── vite-plugin-bh.js — BH compilation │ └── vite-plugin-bem-i18n.js — i18n keysets ├── platforms/ │ ├── desktop.js — entry point for desktop platform │ └── touch.js — entry point for touch platform └── vite.config.js — main Vite config ``` 3. Keep `.enb/` directory intact for fallback #### Phase 5.1: `vite-plugin-bem-levels` — Core BEM Resolution Plugin This is the central piece. The plugin: **5.1.1. Level scanning** — scans BEM levels like ENB does: ```js // Configuration mirrors .enb/config/levels.js const LEVELS = { common: ['common.blocks'], desktop: ['common.blocks', 'desktop.blocks'], touch: ['common.blocks', 'touch.blocks'] }; ``` **5.1.2. Virtual module resolution** — resolves `bem:*` imports: ```js // In source code: import $ from 'bem:jquery'; import bemDom from 'bem:i-bem-dom'; // Plugin resolves 'bem:jquery' → generated barrel file ``` **5.1.3. Barrel file generation for module redefinition chains** For each module with redefinitions, generates a platform-specific barrel file. Based on analysis, there are exactly **5 modules with redefinitions** (8 total redefinition instances): | Module | Files in chain | Generated barrel | |---|---|---| | `jquery` | base + 4 redefinitions (3 common + 1 desktop) | `@bem/desktop/jquery.js`, `@bem/touch/jquery.js` | | `jquery__config` | base (common) + 1 redefinition (desktop) | `@bem/desktop/jquery__config.js` | | `ua` | alternative defs (desktop vs touch) + 1 redefinition (touch) | `@bem/desktop/ua.js`, `@bem/touch/ua.js` | | `events__observable` | base + 1 redefinition | `@bem/common/events__observable.js` | | `i-bem-dom__init` | base + dynamic redefinition | Special handling (see below) | Example generated barrel for `jquery` on desktop platform: ```js // @generated by vite-plugin-bem-levels for platform: desktop import { $ } from '../../common.blocks/jquery/jquery.js'; // Redefinition: pointer events polyfill (mutates $.event.special) import '../../common.blocks/jquery/__event/_type/jquery__event_type_pointernative.js'; // Redefinition: pointerclick event import '../../common.blocks/jquery/__event/_type/jquery__event_type_pointerclick.js'; // Redefinition: pointerpress/pointerrelease events import '../../common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.js'; // Redefinition: IE8 window resize fix (desktop only) import '../../desktop.blocks/jquery/__event/_type/jquery__event_type_winresize.js'; export { $ }; ``` **5.1.4. Automatic redefinition detection** The plugin scans all `.js` files in BEM levels and detects `modules.define('name', ...)` calls: - If a module name appears in multiple files → it's a redefinition chain - Files are ordered by level priority (common < desktop/touch) - Within a level, element/modifier files redefine block files **5.1.5. deps.js → import graph** Parse existing `.deps.js` files and generate import statements: ```js // From: { shouldDeps: [{ block: 'events' }] } // To: import 'bem:events'; ``` This runs as a build-time code generation step, not at runtime. #### Phase 5.2: ym → ES Modules Migration **5.2.1. Source file transformation** — Each `modules.define` file gets an ES module equivalent: Before (ym): ```js modules.define('jquery', ['loader_type_js', 'jquery__config'], function(provide, loader, cfg) { // ... provide(jQuery); }); ``` After (ES module): ```js import loader from 'bem:loader_type_js'; import cfg from 'bem:jquery__config'; let jQuery; // ... loading logic ... export default jQuery; ``` **5.2.2. Redefinition files** — become side-effect imports or wrapper modules: Before (ym redefinition): ```js modules.define('jquery', function(provide, $) { $.event.special.pointerclick = { /* ... */ }; provide($); }); ``` After (ES module side-effect): ```js import $ from 'bem:jquery'; // gets the base jquery $.event.special.pointerclick = { /* ... */ }; // No export needed — this is a side-effect module imported by the barrel ``` **5.2.3. Scope of ym migration** | Category | Count | Migration complexity | |---|---|---| | `.vanilla.js` files (never redefined) | ~13 | Low — straightforward `export default` | | `.js` files (base definitions, no redefinition) | ~30 | Low — `import` deps + `export default` | | `.js` files (redefinition participants) | 14 | Medium — need barrel coordination | | `i-bem-dom__init` dynamic redefinition | 1 | High — needs architectural redesign | **5.2.4. `i-bem-dom__init` special case** The dynamic `modules.define` monkey-patching in `i-bem-dom.js` (lines 1141-1158) cannot be directly expressed in ES modules. Solution: - The Vite plugin generates the `i-bem-dom__init` barrel by scanning which blocks depend on `i-bem-dom` - This replaces the runtime monkey-patching with build-time dependency collection - The barrel imports all BEM DOM blocks, then calls `bemDom.init()` #### Phase 5.3: Template Engine Plugins **5.3.1. `vite-plugin-bemhtml`** - Handles `.bemhtml` and `.bemhtml.js` files - Wraps `bem-xjst` compiler (keep as dependency) - Produces compiled JS that can be imported as ES module - Supports HMR in dev mode **5.3.2. `vite-plugin-bh`** - Handles `.bh.js` files - Wraps `bh` runtime - Produces CommonJS-compatible bundle (BH uses `module.exports`) #### Phase 5.4: i18n Plugin **`vite-plugin-bem-i18n`** - Scans `*.i18n/` directories for keysets - Generates per-language JS modules - Supports `{lang}` placeholder pattern from current ENB config - Integrates with BEMHTML i18n via `bem-xjst` #### Phase 5.5: Vite Configuration ```js // build/vite.config.js import { defineConfig } from 'vite'; import bemLevels from './plugins/vite-plugin-bem-levels.js'; import bemhtml from './plugins/vite-plugin-bemhtml.js'; import bh from './plugins/vite-plugin-bh.js'; import bemI18n from './plugins/vite-plugin-bem-i18n.js'; export default defineConfig(({ mode }) => { const platform = process.env.BEM_PLATFORM || 'desktop'; return { plugins: [ bemLevels({ platform, levels: { common: ['common.blocks'], desktop: ['common.blocks', 'desktop.blocks'], touch: ['common.blocks', 'touch.blocks'] } }), bemhtml(), bh({ jsAttrName: 'data-bem', jsAttrScheme: 'json' }), bemI18n({ langs: ['ru', 'en'] }) ], build: { lib: { entry: `./build/platforms/${platform}.js`, name: 'bemCore', formats: ['es', 'umd'] }, outDir: `dist/${platform}`, rollupOptions: { output: { // Reproduce ENB dist structure: // bem-core.js, bem-core.css, bem-core.bemhtml.js, etc. } } } }; }); ``` #### Phase 5.6: dist Task Replacement ENB `dist` task currently produces these artifacts per platform: | Artifact | dev | min | Vite equivalent | |---|---|---|---| | `bem-core.css` | `.dev.css` | `.css` | Vite CSS output (dev: unminified, build: minified) | | `bem-core.js` | `.dev.js` | `.js` | Vite JS bundle (with autoinit) | | `bem-core.no-autoinit.js` | `.dev.no-autoinit.js` | `.no-autoinit.js` | Separate entry point without `i-bem-dom__init_auto` | | `bem-core.bemhtml.js` | `.dev.bemhtml.js` | `.bemhtml.js` | BEMHTML-only bundle via separate entry | | `bem-core.bh.js` | `.dev.bh.js` | `.bh.js` | BH-only bundle via separate entry | | `bem-core.js+bemhtml.js` | `.dev.js+bemhtml.js` | `.js+bemhtml.js` | Combined bundle (JS + BEMHTML) | | `bem-core.js+bh.js` | `.dev.js+bh.js` | `.js+bh.js` | Combined bundle (JS + BH) | Vite handles dev/production modes natively (no borschik needed). npm scripts: ```json { "build": "npm run build:desktop && npm run build:touch", "build:desktop": "BEM_PLATFORM=desktop vite build -c build/vite.config.js", "build:touch": "BEM_PLATFORM=touch vite build -c build/vite.config.js", "dev": "BEM_PLATFORM=desktop vite -c build/vite.config.js" } ``` #### Phase 5.7: specs/tests/examples Migration 1. **Browser specs** (`*.spec.js`) — Currently use `enb-bem-specs` + mocha-phantomjs: - Migrate to Playwright (already planned in Phase 4) - Vite dev server serves spec pages instead of ENB magic nodes - `vite-plugin-bem-levels` resolves spec level: `libs/bem-pr/spec.blocks` 2. **Examples** (`*.examples/`) — Currently use `enb-bem-examples`: - Vite dev server with HTML plugin serves example pages - BEMJSON → HTML conversion done via imported compiled templates 3. **Template specs** (`tmpl-specs`) — Currently use `enb-bem-tmpl-specs`: - Run as Node.js tests with Mocha (import compiled templates, compare output) #### Phase 5.8: Cleanup 1. Delete `.enb/` directory entirely (17 files) 2. Uninstall all ENB packages (17 packages): ``` npm rm enb enb-bem-techs enb-magic-factory enb-magic-platform \ enb-bemxjst enb-bemxjst-6x enb-bemxjst-7x enb-bemxjst-i18n \ enb-bh enb-bh-i18n enb-borschik enb-css enb-js \ enb-bem-docs enb-bem-examples enb-bem-specs enb-bem-tmpl-specs \ enb-bem-i18n ``` 3. Uninstall `borschik` (replaced by Vite built-in minification) 4. Remove `ym` from production dependencies (replaced by ES modules) 5. Remove `bem-walk` and `bem-naming` if no longer used outside ENB 6. Update `package.json` scripts to use Vite commands #### Phase 5 Sub-Phase Execution Order ``` 5.0 Preparation & coexistence setup │ ├── 5.1 vite-plugin-bem-levels (CRITICAL PATH — everything depends on this) │ ├── 5.1.1 Level scanning │ ├── 5.1.2 Virtual module resolution (bem:* imports) │ ├── 5.1.3 Barrel file generation │ ├── 5.1.4 Redefinition detection │ └── 5.1.5 deps.js parsing │ ├── 5.2 ym → ES modules migration (can start after 5.1 is functional) │ ├── 5.2.1 .vanilla.js files (easiest, start here) │ ├── 5.2.2 .js base definitions │ ├── 5.2.3 .js redefinition files │ └── 5.2.4 i-bem-dom__init special case │ ├── 5.3 Template plugins (parallel with 5.2) │ ├── 5.3.1 vite-plugin-bemhtml │ └── 5.3.2 vite-plugin-bh │ ├── 5.4 i18n plugin (parallel with 5.2, 5.3) │ ├── 5.5 Vite config (after 5.1-5.4 plugins exist) │ ├── 5.6 dist replacement (after 5.5) │ └── Verify output matches ENB dist artifacts │ ├── 5.7 specs/tests/examples (after 5.6) │ └── 5.8 Cleanup (LAST — only after full verification) ``` #### Phase 5 Risk Mitigation | Risk | Impact | Mitigation | |---|---|---| | Barrel file import order matters for side-effects | High | Plugin sorts by level priority; test thoroughly | | `i-bem-dom__init` dynamic dep collection hard to replicate | High | Build-time scanning replaces runtime monkey-patching | | `bem-xjst` may not work as Vite plugin | Medium | Keep as Node.js pre-compilation step if needed | | Circular dependencies between BEM blocks | Medium | Vite handles circular ESM; add cycle detection to plugin | | Output bundle size differs from ENB | Low | Compare sizes; adjust Rollup chunking | | Dev server HMR with BEM redefinitions | Low | Regenerate barrels on file change; full reload as fallback | ### Phase 6: Git Hooks Modernization 1. Install `husky@^9.1.7` + `lint-staged@^16.2.7` 2. Configure `.husky/pre-commit` to run `lint-staged` 3. Configure `lint-staged` in `package.json` to run ESLint on staged files 4. Delete `.githooks/` directory ### Phase 7: CI/CD — Travis CI → GitHub Actions 1. Create `.github/workflows/ci.yml`: - Matrix: Node.js 24 - Steps: install, lint, test - Coverage: c8 + upload to Coveralls 2. Delete `.travis.yml` ### Phase 8: Update Remaining Packages 1. `mocha` → ^11.7.5 2. `jsdoc` → ^4.0.5 3. `vow` → replace with native `Promise` where possible; remove if fully replaced 4. `borschik` → remove (replaced by Vite built-in minification in Phase 5.8) 5. `bem-naming` → remove if only used by ENB plugins; keep if used by `vite-plugin-bem-levels` 6. `bem-walk` → remove (replaced by custom level scanning in `vite-plugin-bem-levels`) 7. `ym` → remove from production deps (replaced by ES modules in Phase 5.2) ### Phase 9: Regenerate Lock File & Validate 1. Run `npm install` to generate new `package-lock.json` (lockfile v3) 2. Run `npm run lint` — fix any ESLint issues 3. Run `npm run build` — verify Vite builds succeed for both platforms 4. Run `npm run test` — verify all tests pass 5. Compare Vite dist output with archived ENB dist output (size, functionality) 6. Run examples in Vite dev server, verify they work --- ## Risk Assessment | Risk | Impact | Mitigation | |------|--------|------------| | `vite-plugin-bem-levels` barrel generation order incorrect | **CRITICAL** | Comprehensive tests comparing ENB and Vite output | | `i-bem-dom__init` dynamic monkey-patching hard to replicate statically | **HIGH** | Build-time BEM block scanning replaces runtime logic | | `bem-xjst` (BEMHTML compiler) integration with Vite plugin | High | Keep as pre-compilation step if direct plugin fails | | Chai 6.x ESM-only breaks test imports | Medium | Use Chai 4.x (last CJS) or add ESM wrapper | | mocha-phantomjs removal breaks browser tests | High | Playwright migration for spec tests | | Circular dependencies between BEM blocks in ES modules | Medium | Vite handles circular ESM natively; add cycle detection | | BEM-specific spec files (`modules.define`) won't work without `ym` | High | Migrate spec files to ES imports as part of Phase 5.2 | | Output bundle size/behavior differs from ENB | Medium | Comparison testing: run both builds, diff output | | ym → ES modules migration introduces regressions | High | Phased migration with coexistence; ENB stays as reference | ## Suggested Execution Order (by priority/safety) 1. **Phase 0** — Baseline 2. **Phase 1** — Node.js 24 (needed for Vite 6 and modern tooling) 3. **Phase 2** — Remove dead packages (safe, reduces surface) 4. **Phase 3** — ESLint migration (independent, high value) 5. **Phase 6** — Git hooks (small, independent) 6. **Phase 7** — GitHub Actions (independent) 7. **Phase 8** — Update remaining packages 8. **Phase 4** — Testing modernization (Playwright needed for Phase 5.7) 9. **Phase 5** — Build system migration to Vite (largest effort): - 5.0 → 5.1 → 5.2 + 5.3 + 5.4 (parallel) → 5.5 → 5.6 → 5.7 → 5.8 10. **Phase 9** — Final validation (verify all Vite builds match ENB output) ================================================ FILE: README.md ================================================ # bem-core library [![CI](https://github.com/bem/bem-core/actions/workflows/ci.yml/badge.svg?branch=v5)](https://github.com/bem/bem-core/actions/workflows/ci.yml) [![GitHub Release](https://img.shields.io/github/release/bem/bem-core.svg)](https://github.com/bem/bem-core/releases) Documentation on `bem-core` is also available at [bem.info](https://en.bem.info/libs/bem-core/). It is also available [in Russian](https://ru.bem.info/libs/bem-core/). ## What is this? `bem-core` is a base library for web interface development. It provides the minimal stack for coding client-side JavaScript and templating. ## Use Install as an npm dependency: ```shell npm install bem-core@5 ``` jQuery 4 is a peer dependency — install it alongside: ```shell npm install jquery@^4.0.0 ``` ## Inside ### Levels - `common.blocks` — suited for any devices and browsers - `desktop.blocks` — should be used for desktop browsers - `touch.blocks` — implement some touch-platforms specifics ### Blocks - `i-bem` — base block with helpers for JS and HTML - `strings` — helpers for JS-strings - `objects` — helpers for JS-objects - `functions` — helpers for JS-functions - `events` — JS-events - `uri` — helpers for work with URIs and querystrings - `tick` — global timer - `idle` — IDLE event - `next-tick` — polyfill for `nextTick`/`setTimeout(0, ...)` - `inherit` — OOP helpers - `jquery` — jQuery - `clearfix` — CSS clearfix trick - `identify` — identify JS-objects - `cookie` — helpers for work with browser cookies - `dom` — helpers for work with DOM - `loader` — loader for JS files - `ua` — browser features detection - `keyboard` — keyboard helpers - `page` — html/head/body scaffold ### Technologies - vanilla.js + browser.js - bemhtml - bemtree ## API The autogenerated JSDoc API can be found on bem.info. E.g. JSDoc for `i-bem` is here https://en.bem.info/platform/libs/bem-core/current/desktop/i-bem/#jsdoc ## Changelog See [CHANGELOG.md](CHANGELOG.md). ## Migration If you are upgrading from v4, see [MIGRATION.md](MIGRATION.md). ## Development ### Working copy 1. Get the source code: ```shell git clone -b v5 git://github.com/bem/bem-core.git cd bem-core ``` 2. Install the dependencies (requires Node.js 20+): ```shell npm install ``` 3. Run linting: ```shell npm run lint ``` 4. Run tests: ```shell npm test # server-side tests (node:test) npm run test:browser # browser tests (Playwright) npm run test:all # both ``` 5. Build: ```shell npm run build # desktop + touch platforms ``` ### How to contribute Please refer to [How to contribute](/CONTRIBUTING.md) guide. ## Supported browsers - Google Chrome (latest) - Firefox (latest) - Safari (latest) - Edge (latest) ## License Code and documentation copyright 2012 YANDEX LLC. Code released under the [Mozilla Public License 2.0](LICENSE.txt). ================================================ FILE: README.ru.md ================================================ # Библиотека BEM Core `bem-core` — это библиотека с открытым кодом, которая предоставляет набор блоков для разработки веб-интерфейсов. Содержит необходимый минимум для разработки клиентского JS и HTML-шаблонов. [![CI](https://github.com/bem/bem-core/actions/workflows/ci.yml/badge.svg?branch=v5)](https://github.com/bem/bem-core/actions/workflows/ci.yml) [![GitHub Release](https://img.shields.io/github/release/bem/bem-core.svg)](https://github.com/bem/bem-core/releases) > **Примечание.** Информация о библиотеке в более информативном виде доступна на [bem.info](https://ru.bem.info/libs/bem-core/). This README is also available [in English](https://en.bem.info/libs/bem-core/). ## Содержание * [Уровни](#Уровни-переопределения) * [Блоки](#Блоки) * [Использование](#Использование) * [Поддерживаемые браузеры](#Поддерживаемые-браузеры) * [Технологии](#Технологии) * [API](#api) * [Разработка](#Разработка) **Дополнительная информация** * [История изменений](CHANGELOG.ru.md) * [Миграция на последующие версии](MIGRATION.ru.md) ## Уровни переопределения * `common.blocks` — поддержка всех устройств и браузеров; * `desktop.blocks` — поддержка всех десктопных браузеров; * `touch.blocks` — реализация специфических особенностей для touch-платформ. ## Блоки * [i-bem](common.blocks/i-bem/i-bem.ru.md) — базовый блок с хелперами для JS и HTML; * [i-bem-dom](common.blocks/i-bem-dom/i-bem-dom.ru.md) — базовый блок с хелперами для HTML; * [strings](common.blocks/strings/strings.ru.md) — хелперы для JS-строк; * [objects](common.blocks/objects/objects.ru.md) — хелперы для JS-объектов; * [functions](common.blocks/functions/functions.ru.md) — хелперы для JS-функций; * [events](common.blocks/events/events.ru.md) — JS-события; * [uri](common.blocks/uri/uri.ru.md) — работа с URI и строкой запроса; * [tick](common.blocks/tick/tick.ru.md) — глобальный таймер; * [idle](common.blocks/idle/idle.ru.md) — IDLE-событие; * [next-tick](common.blocks/next-tick/next-tick.ru.md) — полифил для `nextTick`/`setTimeout(0, ...)`; * [inherit](common.blocks/inherit/inherit.ru.md) — ООП-хелперы; * [jquery](common.blocks/jquery/jquery.ru.md) — jQuery; * [clearfix](common.blocks/clearfix/clearfix.ru.md) — CSS-трюк clearfix; * [identify](common.blocks/identify/identify.ru.md) — идентификация JS-объектов; * [cookie](common.blocks/cookie/cookie.ru.md) — хелперы для работы с браузерными куками; * [dom](common.blocks/dom/dom.ru.md) — хелперы для работы с DOM; * [loader](common.blocks/loader/loader.ru.md) — загрузчик для JS-файлов; * [ua](common.blocks/ua/ua.ru.md) — определение возможностей браузера; * [uri](common.blocks/uri/uri.ru.md) — декодирование строки из формата URI; * [keyboard](common.blocks/keyboard/keyboard.ru.md) — хелперы для работы с клавиатурой; * [page](common.blocks/page/page.ru.md) — скелет для html/head/body. ## Использование Установите как npm-зависимость: ```shell npm install bem-core@5 ``` jQuery 4 — peer-зависимость, установите рядом: ```shell npm install jquery@^4.0.0 ``` ## Поддерживаемые браузеры * Google Chrome (последняя версия) * Firefox (последняя версия) * Safari (последняя версия) * Edge (последняя версия) ## Технологии * vanilla.js + browser.js; * DEPS; * bemhtml; * bemtree. ## API Автосгенерированную документацию на JavaScript API блоков (JSDoc) можно посмотреть на bem.info. Например, для блока `i-bem` она доступна по ссылке https://ru.bem.info/platform/libs/bem-core/current/desktop/i-bem/#jsdoc ## Разработка ### Рабочая копия 1. Получаем исходники: ```bash git clone -b v5 git://github.com/bem/bem-core.git cd bem-core ``` 2. Устанавливаем зависимости (требуется Node.js 20+): ```bash npm install ``` 3. Запускаем линтер: ```bash npm run lint ``` 4. Запускаем тесты: ```bash npm test # серверные тесты (node:test) npm run test:browser # браузерные тесты (Playwright) npm run test:all # все тесты ``` 5. Собираем: ```bash npm run build # desktop + touch платформы ``` ## Команда основной разработки * [veged](https://github.com/veged) * [dfilatov](https://github.com/dfilatov) * [tadatuta](https://github.com/tadatuta) ## Лицензия © 2012 YANDEX LLC. Код лицензирован [Mozilla Public License 2.0](LICENSE.txt). ================================================ FILE: SUMMARY.md ================================================ # Summary * [Введение](README.ru.md) * [Общие сведения](common.docs/i-bem-js/i-bem-js-common.ru.md) * [Привязка JS-блоков к HTML](common.docs/i-bem-js/i-bem-js-html-binding.ru.md) * [Декларация блока](common.docs/i-bem-js/i-bem-js-decl.ru.md) * [Передача параметров](common.docs/i-bem-js/i-bem-js-params.ru.md) * [Работа с DOM-деревом](common.docs/i-bem-js/i-bem-js-dom.ru.md) * [Состояния блока](common.docs/i-bem-js/i-bem-js-states.ru.md) * [Коллекции](common.docs/i-bem-js/i-bem-js-collections.ru.md) * [События](common.docs/i-bem-js/i-bem-js-events.ru.md) * [Инициализация](common.docs/i-bem-js/i-bem-js-init.ru.md) * [Взаимодействие блоков](common.docs/i-bem-js/i-bem-js-interact.ru.md) * [Контекст](common.docs/i-bem-js/i-bem-js-context.ru.md) * [Что дальше?](common.docs/i-bem-js/i-bem-js-extras.ru.md) ================================================ FILE: build/platforms/desktop.js ================================================ /** * Entry point for the desktop platform build. * * This file imports all BEM blocks in the correct order for the desktop platform. * The vite-plugin-bem-levels plugin resolves `bem:*` imports to actual files * from the level chain: common.blocks → desktop.blocks */ // Core utilities (no dependencies) import 'bem:identify'; import 'bem:inherit'; import 'bem:objects'; import 'bem:functions'; import 'bem:functions__throttle'; import 'bem:functions__debounce'; import 'bem:next-tick'; import 'bem:strings__escape'; // Events system import 'bem:events'; import 'bem:events__channels'; // i-bem core import 'bem:i-bem__internal'; import 'bem:i-bem'; import 'bem:i-bem__collection'; // DOM utilities import 'bem:jquery__config'; import 'bem:jquery'; import 'bem:jquery__event_type_winresize'; import 'bem:dom'; // i-bem-dom and its subsystems import 'bem:i-bem-dom__events'; import 'bem:i-bem-dom__events_type_dom'; import 'bem:i-bem-dom__events_type_bem'; import 'bem:i-bem-dom__collection'; import 'bem:i-bem-dom'; import 'bem:i-bem-dom__init'; import 'bem:i-bem-dom__init_auto'; // Observable events (with BEM DOM support) import 'bem:events__observable'; import 'bem:events__observable_type_bem-dom'; // Browser APIs import 'bem:cookie'; import 'bem:idle'; import 'bem:idle_start_auto'; import 'bem:tick'; import 'bem:tick_start_auto'; import 'bem:keyboard__codes'; import 'bem:ua'; // URI utilities import 'bem:uri'; import 'bem:uri__querystring'; // Loaders import 'bem:loader_type_js'; import 'bem:loader_type_bundle'; ================================================ FILE: build/platforms/touch.js ================================================ /** * Entry point for the touch platform build. * * This file imports all BEM blocks in the correct order for the touch platform. * The vite-plugin-bem-levels plugin resolves `bem:*` imports to actual files * from the level chain: common.blocks → touch.blocks */ // Core utilities (no dependencies) import 'bem:identify'; import 'bem:inherit'; import 'bem:objects'; import 'bem:functions'; import 'bem:functions__throttle'; import 'bem:functions__debounce'; import 'bem:next-tick'; import 'bem:strings__escape'; // Events system import 'bem:events'; import 'bem:events__channels'; // i-bem core import 'bem:i-bem__internal'; import 'bem:i-bem'; import 'bem:i-bem__collection'; // DOM utilities import 'bem:jquery__config'; import 'bem:jquery'; import 'bem:dom'; // i-bem-dom and its subsystems import 'bem:i-bem-dom__events'; import 'bem:i-bem-dom__events_type_dom'; import 'bem:i-bem-dom__events_type_bem'; import 'bem:i-bem-dom__collection'; import 'bem:i-bem-dom'; import 'bem:i-bem-dom__init'; import 'bem:i-bem-dom__init_auto'; // Observable events (with BEM DOM support) import 'bem:events__observable'; import 'bem:events__observable_type_bem-dom'; // Browser APIs import 'bem:cookie'; import 'bem:idle'; import 'bem:idle_start_auto'; import 'bem:tick'; import 'bem:tick_start_auto'; import 'bem:keyboard__codes'; import 'bem:ua'; import 'bem:ua__dom'; // URI utilities import 'bem:uri'; import 'bem:uri__querystring'; // Loaders import 'bem:loader_type_js'; import 'bem:loader_type_bundle'; ================================================ FILE: build/plugins/vite-plugin-bem-levels.js ================================================ import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs' import { join, resolve, relative, dirname, basename } from 'node:path' const BEM_PREFIX = 'bem:' const VIRTUAL_PREFIX = '\0bem:' /** * Scans a BEM level directory and returns all BEM entities found. * * BEM nested file structure: * block/ * block.js * block.deps.js * __elem/ * block__elem.js * _mod/ * block_mod.js * block_mod_val.js * * @param {string} levelDir - absolute path to a BEM level directory * @returns {Map} moduleName → array of file entries */ function scanLevel(levelDir) { const modules = new Map() if (!existsSync(levelDir)) return modules const blocks = readdirSync(levelDir).filter(name => { const fullPath = join(levelDir, name) return statSync(fullPath).isDirectory() && !name.startsWith('.') }) for (const block of blocks) { const blockDir = join(levelDir, block) scanDirectory(blockDir, modules, levelDir) } return modules } /** * Recursively scans a BEM entity directory for JS source files. * Module names are derived from file names per BEM naming convention — * file contents are never read during scanning. * * Only recurses into BEM-named subdirectories: * __elemName (element) or _modName (modifier). * This automatically excludes .tests/, .examples/, .tmpl-specs/, etc. */ function scanDirectory(dir, modules, levelDir) { let entries try { entries = readdirSync(dir) } catch { return } for (const entry of entries) { const fullPath = join(dir, entry) let stat try { stat = statSync(fullPath) } catch { continue } if (stat.isDirectory()) { // Only recurse into BEM-named subdirectories: __elem or _mod if (entry.startsWith('__') || entry.startsWith('_')) { scanDirectory(fullPath, modules, levelDir) } continue } if (!stat.isFile()) continue // Consider JS source files and .post.css files const isPostCss = entry.endsWith('.post.css') const isVanillaJs = entry.endsWith('.vanilla.js') const isPlainJs = !isVanillaJs && entry.endsWith('.js') && !entry.endsWith('.deps.js') && !entry.endsWith('.spec.js') && !entry.endsWith('.bemhtml.js') && !entry.endsWith('.bh.js') && !entry.endsWith('.bemjson.js') && !entry.endsWith('.test.js') && !entry.endsWith('.i18n.js') if (!isVanillaJs && !isPlainJs && !isPostCss) continue // Module name is derived from the filename per BEM naming convention const name = filePathToModuleName(fullPath, levelDir) if (!name) continue const suffix = isPostCss ? '.post.css' : (isVanillaJs ? '.vanilla.js' : '.js') const existing = modules.get(name) || [] existing.push({ name, filePath: fullPath, suffix, levelDir, }) modules.set(name, existing) } } /** * Parses a modules.define() call from source code. * Returns { name, deps, callbackParamCount, isRedefinition } or null. * * In ym, a redefinition is detected by the callback having one extra parameter * beyond provide + deps. That extra parameter receives the previous module value. * * Base: modules.define('name', ['dep1'], function(provide, dep1) { ... }) * → callbackParams = 2, isRedefinition = false * * Redefinition: modules.define('name', ['dep1'], function(provide, dep1, prev) { ... }) * → callbackParams = 3, isRedefinition = true */ function parseModulesDefine(source) { // Match modules.define('name' or modules.define("name" const defineMatch = source.match( /modules\.define\s*\(\s*(['"])([^'"]+)\1/ ) if (!defineMatch) return null const name = defineMatch[2] // Try to extract dependency array // Look for the pattern after the name: , ['dep1', 'dep2'] const afterName = source.slice(defineMatch.index + defineMatch[0].length) const depsMatch = afterName.match( /^\s*,\s*\[([^\]]*)\]/ ) const deps = [] if (depsMatch) { const depsStr = depsMatch[1] const depPattern = /['"]([^'"]+)['"]/g let m while ((m = depPattern.exec(depsStr)) !== null) { deps.push(m[1]) } } // Extract callback parameter count to detect redefinitions. // Look for `function(` after the deps array (or after the name if no deps). const afterDeps = depsMatch ? afterName.slice(depsMatch[0].length) : afterName const callbackMatch = afterDeps.match( /,\s*function\s*\(([^)]*)\)/ ) let callbackParamCount = 0 if (callbackMatch) { const params = callbackMatch[1].trim() callbackParamCount = params ? params.split(/\s*,\s*/).length : 0 } // A redefinition has more callback params than provide (1) + deps count. // The extra parameter receives the previous module value. const isRedefinition = callbackParamCount > 1 + deps.length return { name, deps, callbackParamCount, isRedefinition } } /** * Parses a migrated ES module file. * Detects `export default` and derives the module name from the file path. * * For redefinitions (transformer functions), detects: * export default function(prev) { ... } * * Extracts `import ... from 'bem:...'` as dependencies. * * @param {string} source - file content * @param {string} filePath - absolute path to the file * @param {string} levelDir - absolute path to the level directory * @returns {{ name: string, deps: string[], isRedefinition: boolean } | null} */ function parseEsModule(source, filePath, levelDir) { // Must have export default if (!/export\s+default\b/.test(source)) return null // Derive module name from file path using BEM naming const name = filePathToModuleName(filePath, levelDir) if (!name) return null // Extract bem: imports as dependencies const deps = [] const importPattern = /import\s+\w+\s+from\s+['"]bem:([^'"]+)['"]/g let m while ((m = importPattern.exec(source)) !== null) { deps.push(m[1]) } // Detect if this is a transformer (redefinition): // export default function(prev) { ... } // The pattern is: export default function with exactly one parameter const isRedefinition = /export\s+default\s+function\s*\([^)]+\)\s*\{/.test(source) return { name, deps, isRedefinition } } /** * Derives a BEM module name from a file path. * * common.blocks/objects/objects.vanilla.js → 'objects' * common.blocks/i-bem/__internal/i-bem__internal.vanilla.js → 'i-bem__internal' * common.blocks/functions/__debounce/functions__debounce.vanilla.js → 'functions__debounce' * common.blocks/loader/_type/loader_type_js.js → 'loader_type_js' */ function filePathToModuleName(filePath, levelDir) { const rel = relative(levelDir, filePath) // Get the filename without extensions const fileName = basename(rel) // Strip .vanilla.js, .js, or .post.css const name = fileName.replace(/\.(vanilla\.js|js|post\.css)$/, '') return name || null } /** * Parses a .deps.js file and returns the dependency declarations. * * deps.js format: * ({ shouldDeps: [...], mustDeps: [...], noDeps: [...] }) * or * ([{ shouldDeps: ... }, { ... }]) */ function parseDepsFile(filePath) { if (!existsSync(filePath)) return null const content = readFileSync(filePath, 'utf8') try { // deps.js files are wrapped in parentheses: ({ ... }) or ([...]) // Use Function constructor to evaluate (safer than eval, no access to scope) const fn = new Function('return ' + content) const result = fn() return normalizeDeps(result) } catch { return null } } /** * Normalizes deps.js result into { mustDeps: [], shouldDeps: [], noDeps: [] } */ function normalizeDeps(raw) { if (Array.isArray(raw)) { // Array of dep declarations — merge them const merged = { mustDeps: [], shouldDeps: [], noDeps: [] } for (const item of raw) { const norm = normalizeDeps(item) if (norm) { merged.mustDeps.push(...norm.mustDeps) merged.shouldDeps.push(...norm.shouldDeps) merged.noDeps.push(...norm.noDeps) } } return merged } if (raw && typeof raw === 'object') { return { mustDeps: normalizeDep(raw.mustDeps || []), shouldDeps: normalizeDep(raw.shouldDeps || []), noDeps: normalizeDep(raw.noDeps || []), } } return null } /** * Normalizes a single deps declaration value to an array of BEM entity references. * Input can be: string | object | array */ function normalizeDep(dep) { if (!dep) return [] if (typeof dep === 'string') return [{ block: dep }] if (Array.isArray(dep)) return dep.flatMap(d => normalizeDep(d)) if (typeof dep === 'object') { // Could be { block: 'name' } or { elem: 'name' } or { mods: {...} } etc. return [dep] } return [] } /** * Converts a BEM entity reference to a module name. * { block: 'jquery', elem: 'event', mods: { type: 'pointer' } } * → 'jquery__event_type_pointer' * * This is a simplified conversion — real BEM naming is more complex, * but for the modules actually defined in bem-core, this covers all cases. */ function bemEntityToModuleName(entity, contextBlock) { if (typeof entity === 'string') return entity const block = entity.block || contextBlock if (!block) return null let name = block // Handle elem / elems if (entity.elem) { name += '__' + entity.elem } // Handle mod / mods if (entity.mod) { name += '_' + entity.mod if (entity.val && entity.val !== true) { name += '_' + entity.val } } if (entity.mods) { for (const [mod, vals] of Object.entries(entity.mods)) { if (Array.isArray(vals)) { // Multiple values → multiple modules (e.g., type: ['dom', 'bem']) // Return only the first for now; caller should handle arrays name += '_' + mod } else if (vals === true) { name += '_' + mod } else { name += '_' + mod + '_' + vals } } } return name } /** * Expands a BEM dependency entity into one or more module names. * Handles elems (array) and mods (array values). */ function expandBemEntity(entity, contextBlock) { if (typeof entity === 'string') return [entity] const block = entity.block || contextBlock if (!block) return [] const results = [] // If entity has elems (array of elements), expand each if (entity.elems) { const elems = Array.isArray(entity.elems) ? entity.elems : [entity.elems] for (const elem of elems) { if (typeof elem === 'string') { results.push(block + '__' + elem) } else if (elem && typeof elem === 'object') { // { elem: 'init', mods: { auto: true } } const elemName = elem.elem results.push(block + '__' + elemName) if (elem.mods) { for (const [mod, vals] of Object.entries(elem.mods)) { const modVals = Array.isArray(vals) ? vals : [vals] for (const val of modVals) { if (val === true) { results.push(block + '__' + elemName + '_' + mod) } else { results.push(block + '__' + elemName + '_' + mod + '_' + val) } } } } } } return results } // If entity has elem (single) if (entity.elem) { const base = block + '__' + entity.elem if (entity.mods) { for (const [mod, vals] of Object.entries(entity.mods)) { const modVals = Array.isArray(vals) ? vals : [vals] for (const val of modVals) { if (val === true) { results.push(base + '_' + mod) } else { results.push(base + '_' + mod + '_' + val) } } } return results } return [base] } // Block with mods if (entity.mods) { for (const [mod, vals] of Object.entries(entity.mods)) { const modVals = Array.isArray(vals) ? vals : [vals] for (const val of modVals) { if (val === true) { results.push(block + '_' + mod) } else { results.push(block + '_' + mod + '_' + val) } } } return results } // Simple block reference return [block] } /** * Build a complete module registry from BEM levels. * * @param {string[]} levels - ordered list of level directories (e.g., ['common.blocks', 'desktop.blocks']) * @param {string} rootDir - project root directory * @returns {{ modules: Map, deps: Map, redefinitions: Map }} */ function buildRegistry(levels, rootDir) { // moduleName → [{ name, deps, filePath, suffix, levelDir, levelIndex }] const allModules = new Map() for (let i = 0; i < levels.length; i++) { const levelDir = resolve(rootDir, levels[i]) const levelModules = scanLevel(levelDir) for (const [name, entries] of levelModules) { const existing = allModules.get(name) || [] for (const entry of entries) { entry.levelIndex = i } existing.push(...entries) allModules.set(name, existing) } } // Detect cross-level redefinitions: same module name from different levels. // First entry (lowest level index) is the base, subsequent entries are redefinitions. const redefinitions = new Map() for (const [name, entries] of allModules) { entries.sort((a, b) => a.levelIndex - b.levelIndex) if (entries.length > 1) { redefinitions.set(name, entries) } } // Collect deps.js files const depsMap = new Map() for (const [name, entries] of allModules) { for (const entry of entries) { // Find corresponding .deps.js file // e.g., common.blocks/jquery/jquery.js → common.blocks/jquery/jquery.deps.js const dir = dirname(entry.filePath) const possibleDepsFiles = [ // Same directory, same base name entry.filePath.replace(/\.(vanilla\.)?js$/, '.deps.js'), ] // Also look for block-level deps.js const blockDir = dirname(dir) === entry.levelDir ? dir : dirname(dir) const blockName = basename(blockDir) const blockDeps = join(blockDir, blockName + '.deps.js') if (!possibleDepsFiles.includes(blockDeps)) { possibleDepsFiles.push(blockDeps) } for (const depsFile of possibleDepsFiles) { if (existsSync(depsFile) && !depsMap.has(depsFile)) { const parsed = parseDepsFile(depsFile) if (parsed) { depsMap.set(depsFile, { ...parsed, forModule: name }) } } } } } // Separate CSS entries from JS entries. // CSS files are side-effect imports, not module redefinitions. const cssModules = new Map() for (const [name, entries] of allModules) { const cssEntries = entries.filter(e => e.suffix === '.post.css') const jsEntries = entries.filter(e => e.suffix !== '.post.css') if (cssEntries.length > 0) { cssModules.set(name, cssEntries) } if (jsEntries.length > 0) { allModules.set(name, jsEntries) } else if (cssEntries.length > 0) { // CSS-only module — keep in allModules so it can be resolved allModules.set(name, []) } } // Recompute redefinitions after removing CSS entries redefinitions.clear() for (const [name, entries] of allModules) { if (entries.length > 1) { redefinitions.set(name, entries) } } return { modules: allModules, deps: depsMap, redefinitions, cssModules } } /** * Generate a barrel (re-export) module for a module with redefinitions. * * In ym, each redefinition receives the previous module value as its last * callback parameter and calls provide() with a new/modified value. * * In ES modules, we model this as: * - Base file: `export default value;` * - Redefinition file: `export default function(prev) { return newValue; }` * * The barrel chains them: * import _base from './base.js' * import _redef0 from './redef0.js' * import _redef1 from './redef1.js' * let _module = _base * _module = _redef0(_module) * _module = _redef1(_module) * export default _module * * @param {string} name - module name * @param {object[]} entries - sorted array of file entries (base + redefinitions) * @param {string} rootDir - project root * @returns {string} generated ES module source code */ function generateBarrel(name, entries, rootDir) { const lines = [`// @generated by vite-plugin-bem-levels`] const base = entries[0] const basePath = './' + relative(rootDir, base.filePath).replace(/\\/g, '/') const baseId = safeIdentifier(name) + '_base' lines.push(`import ${baseId} from '${basePath}';`) // Import each redefinition as a named transformer const redefIds = [] for (let i = 1; i < entries.length; i++) { const redef = entries[i] const redefPath = './' + relative(rootDir, redef.filePath).replace(/\\/g, '/') const redefId = safeIdentifier(name) + '_redef' + (i - 1) lines.push(`import ${redefId} from '${redefPath}';`) redefIds.push(redefId) } lines.push('') lines.push(`let _module = ${baseId};`) for (const redefId of redefIds) { lines.push(`_module = ${redefId}(_module);`) } lines.push(`export default _module;`) return lines.join('\n') } /** * Generate a safe JavaScript identifier from a BEM module name. * 'jquery__config' → '_jquery__config' * 'i-bem-dom' → '_iBemDom' */ function safeIdentifier(name) { // Replace hyphens with camelCase, prefix with underscore let id = name .replace(/-([a-z])/g, (_, c) => c.toUpperCase()) .replace(/-/g, '_') // Ensure starts with valid identifier char if (/^[0-9]/.test(id)) id = '_' + id return '_' + id } /** * Vite plugin for BEM level resolution and barrel file generation. * * @param {object} options * @param {string} options.platform - 'desktop' or 'touch' * @param {object} options.levels - platform → level directories mapping * @param {string} [options.rootDir] - project root (default: process.cwd()) */ export default function bemLevels(options = {}) { const { platform = 'desktop', levels = { common: ['common.blocks'], desktop: ['common.blocks', 'desktop.blocks'], touch: ['common.blocks', 'touch.blocks'], }, rootDir = process.cwd(), } = options const platformLevels = levels[platform] if (!platformLevels) { throw new Error(`Unknown platform: ${platform}. Available: ${Object.keys(levels).join(', ')}`) } let registry = null function getRegistry() { if (!registry) { registry = buildRegistry(platformLevels, rootDir) } return registry } return { name: 'vite-plugin-bem-levels', resolveId(id) { if (id.startsWith(BEM_PREFIX)) { return VIRTUAL_PREFIX + id.slice(BEM_PREFIX.length) } return null }, load(id) { if (!id.startsWith(VIRTUAL_PREFIX)) return null const moduleName = id.slice(VIRTUAL_PREFIX.length) const reg = getRegistry() const entries = reg.modules.get(moduleName) const cssEntries = reg.cssModules ? reg.cssModules.get(moduleName) : null if ((!entries || entries.length === 0) && !cssEntries) { this.error(`BEM module not found: ${moduleName}`) return null } // Generate CSS side-effect imports const cssImports = [] if (cssEntries) { for (const cssEntry of cssEntries) { const cssPath = './' + relative(rootDir, cssEntry.filePath).replace(/\\/g, '/') cssImports.push(`import '${cssPath}';`) } } // CSS-only module (no JS) if (!entries || entries.length === 0) { return cssImports.join('\n') + '\n' } // If the module has redefinitions, generate a barrel if (entries.length > 1) { const barrel = generateBarrel(moduleName, entries, rootDir) if (cssImports.length > 0) { return cssImports.join('\n') + '\n' + barrel } return barrel } // Single definition — re-export or side-effect import const entry = entries[0] const entryPath = './' + relative(rootDir, entry.filePath).replace(/\\/g, '/') const source = readFileSync(entry.filePath, 'utf8') const hasDefaultExport = /export\s+default\b/.test(source) const jsCode = hasDefaultExport ? `export { default } from '${entryPath}';\n` : `import '${entryPath}';\n` if (cssImports.length > 0) { return cssImports.join('\n') + '\n' + jsCode } return jsCode }, // Invalidate registry on file changes in BEM levels configureServer(server) { const levelDirs = platformLevels.map(l => resolve(rootDir, l)) server.watcher.on('all', (event, filePath) => { for (const levelDir of levelDirs) { if (filePath.startsWith(levelDir)) { registry = null; // invalidate cache break } } }) }, // Expose registry for testing and introspection api: { getRegistry() { return getRegistry(); }, scanLevel, parseModulesDefine, parseDepsFile, normalizeDeps, expandBemEntity, generateBarrel, buildRegistry, }, } } // Named exports for testing export { scanLevel, parseModulesDefine, parseEsModule, filePathToModuleName, parseDepsFile, normalizeDeps, normalizeDep, bemEntityToModuleName, expandBemEntity, buildRegistry, generateBarrel, safeIdentifier, } ================================================ FILE: build/plugins/vite-plugin-bem-levels.test.js ================================================ import { describe, it } from 'node:test'; import { strict as assert } from 'node:assert'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { scanLevel, parseModulesDefine, parseDepsFile, expandBemEntity, buildRegistry, generateBarrel, safeIdentifier, } from './vite-plugin-bem-levels.js'; const ROOT = resolve(import.meta.dirname, '../..'); // --- parseModulesDefine --- describe('parseModulesDefine', function() { it('parses simple define without deps', function() { const result = parseModulesDefine( "modules.define('cookie', function(provide) { provide({}); });" ); assert.strictEqual(result.name, 'cookie'); assert.deepStrictEqual(result.deps, []); assert.strictEqual(result.callbackParamCount, 1); assert.strictEqual(result.isRedefinition, false); }); it('parses define with dependency array', function() { const result = parseModulesDefine( "modules.define('i-bem', ['i-bem__internal', 'inherit'], function(provide, internal, inherit) {});" ); assert.strictEqual(result.name, 'i-bem'); assert.deepStrictEqual(result.deps, ['i-bem__internal', 'inherit']); assert.strictEqual(result.callbackParamCount, 3); assert.strictEqual(result.isRedefinition, false); }); it('parses define with double quotes', function() { const result = parseModulesDefine( 'modules.define("jquery", ["loader_type_js"], function(provide, loader) {});' ); assert.strictEqual(result.name, 'jquery'); assert.deepStrictEqual(result.deps, ['loader_type_js']); assert.strictEqual(result.isRedefinition, false); }); it('returns null for non-module files', function() { const result = parseModulesDefine('var x = 1;'); assert.strictEqual(result, null); }); it('handles multiline define', function() { const result = parseModulesDefine(` modules.define( 'i-bem-dom', [ 'i-bem', 'i-bem__internal', 'inherit', 'identify', 'objects', 'functions', 'jquery', 'dom' ], function(provide, BEM, BEMINTERNAL, inherit, identify, objects, functions, $, dom) { }); `); assert.strictEqual(result.name, 'i-bem-dom'); assert.deepStrictEqual(result.deps, [ 'i-bem', 'i-bem__internal', 'inherit', 'identify', 'objects', 'functions', 'jquery', 'dom', ]); assert.strictEqual(result.callbackParamCount, 9); assert.strictEqual(result.isRedefinition, false); }); }); // --- parseModulesDefine: redefinition detection --- describe('parseModulesDefine (redefinition detection)', function() { it('detects redefinition: jquery__config on desktop (2 deps + prev)', function() { const result = parseModulesDefine( "modules.define('jquery__config', ['ua', 'objects'], function(provide, ua, objects, base) {});" ); assert.strictEqual(result.name, 'jquery__config'); assert.deepStrictEqual(result.deps, ['ua', 'objects']); assert.strictEqual(result.callbackParamCount, 4); assert.strictEqual(result.isRedefinition, true); }); it('detects redefinition: jquery extension (1 dep + prev)', function() { const result = parseModulesDefine( "modules.define('jquery', ['next-tick'], function(provide, nextTick, $) {});" ); assert.strictEqual(result.isRedefinition, true); assert.strictEqual(result.callbackParamCount, 3); }); it('detects redefinition: jquery extension (0 deps + prev)', function() { const result = parseModulesDefine( "modules.define('jquery', function(provide, $) {});" ); assert.strictEqual(result.isRedefinition, true); assert.strictEqual(result.callbackParamCount, 2); }); it('detects redefinition: events__observable type_bem-dom (1 dep + prev)', function() { const result = parseModulesDefine( "modules.define('events__observable', ['i-bem-dom'], function(provide, bemDom, observable) {});" ); assert.strictEqual(result.isRedefinition, true); assert.strictEqual(result.callbackParamCount, 3); }); it('detects redefinition: ua__dom on touch (1 dep + prev)', function() { const result = parseModulesDefine( "modules.define('ua', ['i-bem-dom'], function(provide, bemDom, ua) {});" ); assert.strictEqual(result.isRedefinition, true); }); it('does NOT detect base as redefinition: cookie (0 deps, 1 param)', function() { const result = parseModulesDefine( "modules.define('cookie', function(provide) { provide({}); });" ); assert.strictEqual(result.isRedefinition, false); }); it('does NOT detect base as redefinition: events (3 deps, 4 params)', function() { const result = parseModulesDefine( "modules.define('events', ['identify', 'inherit', 'functions'], function(provide, identify, inherit, functions) {});" ); assert.strictEqual(result.isRedefinition, false); assert.strictEqual(result.callbackParamCount, 4); }); }); // --- parseModulesDefine: real file detection --- describe('parseModulesDefine (real file cross-check)', function() { it('real file: jquery base is now ESM (migrated)', function() { const source = readFileSync(resolve(ROOT, 'common.blocks/jquery/jquery.js'), 'utf8'); const result = parseModulesDefine(source); assert.strictEqual(result, null, 'migrated ESM file has no modules.define'); assert.ok(source.includes('export default'), 'should have export default'); }); it('real file: jquery__config base is now ESM (migrated)', function() { const source = readFileSync( resolve(ROOT, 'common.blocks/jquery/__config/jquery__config.js'), 'utf8' ); const result = parseModulesDefine(source); assert.strictEqual(result, null, 'migrated ESM file has no modules.define'); assert.ok(source.includes('export default'), 'should have export default'); }); it('real file: jquery__config desktop is now ESM (migrated)', function() { const source = readFileSync( resolve(ROOT, 'desktop.blocks/jquery/__config/jquery__config.js'), 'utf8' ); const result = parseModulesDefine(source); assert.strictEqual(result, null, 'migrated ESM file has no modules.define'); assert.ok(source.includes('export default'), 'should have export default'); }); it('real file: events__observable base is now ESM (migrated)', function() { const source = readFileSync( resolve(ROOT, 'common.blocks/events/__observable/events__observable.js'), 'utf8' ); const result = parseModulesDefine(source); assert.strictEqual(result, null, 'migrated ESM file has no modules.define'); assert.ok(source.includes('export default'), 'should have export default'); }); it('real file: events__observable type_bem-dom is now ESM (migrated)', function() { const source = readFileSync( resolve(ROOT, 'common.blocks/events/__observable/_type/events__observable_type_bem-dom.js'), 'utf8' ); const result = parseModulesDefine(source); assert.strictEqual(result, null, 'migrated ESM file has no modules.define'); assert.ok(source.includes('export default'), 'should have export default'); }); it('real file: ua touch base is now ESM (migrated)', function() { const source = readFileSync( resolve(ROOT, 'touch.blocks/ua/ua.js'), 'utf8' ); const result = parseModulesDefine(source); assert.strictEqual(result, null, 'migrated ESM file has no modules.define'); assert.ok(source.includes('export default'), 'should have export default'); }); it('real file: ua__dom touch is now ESM (migrated)', function() { const source = readFileSync( resolve(ROOT, 'touch.blocks/ua/__dom/ua__dom.js'), 'utf8' ); const result = parseModulesDefine(source); assert.strictEqual(result, null, 'migrated ESM file has no modules.define'); assert.ok(source.includes('export default'), 'should have export default'); }); it('real file: desktop winresize is now ESM (migrated)', function() { const source = readFileSync( resolve(ROOT, 'desktop.blocks/jquery/__event/_type/jquery__event_type_winresize.js'), 'utf8' ); const result = parseModulesDefine(source); assert.strictEqual(result, null, 'migrated ESM file has no modules.define'); assert.ok(source.includes('export default'), 'should have export default'); }); }); // --- scanLevel --- describe('scanLevel', function() { it('scans common.blocks and finds modules', function() { const modules = scanLevel(resolve(ROOT, 'common.blocks')); assert.ok(modules.size > 0, 'Should find modules'); assert.ok(modules.has('jquery'), 'Should find jquery'); assert.ok(modules.has('i-bem'), 'Should find i-bem'); assert.ok(modules.has('i-bem-dom'), 'Should find i-bem-dom'); assert.ok(modules.has('events'), 'Should find events'); assert.ok(modules.has('cookie'), 'Should find cookie'); assert.ok(modules.has('objects'), 'Should find objects'); assert.ok(modules.has('inherit'), 'Should find inherit'); }); it('finds correct file suffixes', function() { const modules = scanLevel(resolve(ROOT, 'common.blocks')); const objects = modules.get('objects'); assert.ok(objects, 'objects should exist'); assert.strictEqual(objects[0].suffix, '.vanilla.js'); const cookie = modules.get('cookie'); assert.ok(cookie, 'cookie should exist'); assert.strictEqual(cookie[0].suffix, '.js'); }); it('scans desktop.blocks', function() { const modules = scanLevel(resolve(ROOT, 'desktop.blocks')); assert.ok(modules.has('ua'), 'Should find ua on desktop'); assert.ok(modules.has('jquery__config'), 'Should find jquery__config on desktop'); }); it('scans touch.blocks', function() { const modules = scanLevel(resolve(ROOT, 'touch.blocks')); assert.ok(modules.has('ua'), 'Should find ua on touch'); }); it('returns empty map for non-existent directory', function() { const modules = scanLevel(resolve(ROOT, 'nonexistent.blocks')); assert.strictEqual(modules.size, 0); }); it('derives module name from filename, not file content', function() { const modules = scanLevel(resolve(ROOT, 'common.blocks')); assert.ok(modules.has('events__observable_type_bem-dom'), 'Should derive module name from filename'); const jqEntries = modules.get('jquery'); assert.ok(jqEntries, 'jquery base should still exist'); assert.strictEqual(jqEntries.length, 1, 'jquery should have exactly 1 entry (base only, no within-level redefinitions)'); }); it('finds new modules from BEM naming', function() { const modules = scanLevel(resolve(ROOT, 'common.blocks')); assert.ok(modules.has('events__observable_type_bem-dom')); assert.ok(modules.has('tick_start_auto')); assert.ok(modules.has('idle_start_auto')); assert.ok(modules.has('i-bem-dom__init_auto')); }); }); // --- buildRegistry --- describe('buildRegistry', function() { it('builds registry for desktop platform', function() { const reg = buildRegistry(['common.blocks', 'desktop.blocks'], ROOT); assert.ok(reg.modules.size > 0); assert.ok(reg.modules.has('jquery')); assert.ok(reg.modules.has('ua')); assert.ok(reg.modules.has('i-bem-dom')); }); it('builds registry for touch platform', function() { const reg = buildRegistry(['common.blocks', 'touch.blocks'], ROOT); assert.ok(reg.modules.has('ua')); const uaEntries = reg.modules.get('ua'); assert.strictEqual(uaEntries.length, 1, 'ua has single touch definition'); assert.ok(reg.modules.has('ua__dom'), 'ua__dom is a separate module'); }); it('jquery has single entry per level (BEM naming)', function() { const reg = buildRegistry(['common.blocks', 'desktop.blocks'], ROOT); const jquery = reg.modules.get('jquery'); assert.ok(jquery, 'jquery should exist'); assert.strictEqual(jquery.length, 1, 'jquery should have exactly 1 entry (only common.blocks/jquery/jquery.js)'); }); it('detects jquery__config cross-level redefinition on desktop', function() { const reg = buildRegistry(['common.blocks', 'desktop.blocks'], ROOT); assert.ok(reg.redefinitions.has('jquery__config'), 'jquery__config should have redefinitions'); const entries = reg.redefinitions.get('jquery__config'); assert.strictEqual(entries.length, 2); assert.ok(entries[0].filePath.includes('common.blocks')); assert.ok(entries[1].filePath.includes('desktop.blocks')); }); it('events__observable has no cross-level redefinition (type_bem-dom is separate module)', function() { const reg = buildRegistry(['common.blocks', 'desktop.blocks'], ROOT); assert.ok(!reg.redefinitions.has('events__observable'), 'events__observable should NOT have redefinitions — type_bem-dom is a separate module'); assert.ok(reg.modules.has('events__observable_type_bem-dom'), 'events__observable_type_bem-dom should be its own module'); }); it('ua has single entry per platform (no common.blocks/ua/ua.js)', function() { const regDesktop = buildRegistry(['common.blocks', 'desktop.blocks'], ROOT); const uaDesktop = regDesktop.modules.get('ua'); assert.ok(uaDesktop, 'ua should exist on desktop'); assert.strictEqual(uaDesktop.length, 1, 'desktop ua has 1 entry'); const regTouch = buildRegistry(['common.blocks', 'touch.blocks'], ROOT); const uaTouch = regTouch.modules.get('ua'); assert.ok(uaTouch, 'ua should exist on touch'); assert.strictEqual(uaTouch.length, 1, 'touch ua has 1 entry'); assert.ok(regTouch.modules.has('ua__dom'), 'ua__dom should be its own module on touch'); }); it('detects i-bem-dom__init definition', function() { const reg = buildRegistry(['common.blocks', 'desktop.blocks'], ROOT); const init = reg.modules.get('i-bem-dom__init'); assert.ok(init, 'i-bem-dom__init should exist'); assert.ok(init.length >= 1, 'i-bem-dom__init should have at least base definition'); }); it('finds all expected modules (complete inventory, BEM naming)', function() { const reg = buildRegistry(['common.blocks', 'desktop.blocks'], ROOT); const expectedModules = [ // common.blocks .vanilla.js 'objects', 'identify', 'functions', 'inherit', 'i-bem', 'tick', 'tick_start_auto', 'uri', 'next-tick', 'events', 'functions__throttle', 'functions__debounce', 'i-bem__internal', 'uri__querystring', 'strings__escape', 'events__channels', // common.blocks .js 'cookie', 'dom', 'jquery', 'idle', 'idle_start_auto', 'keyboard__codes', 'loader_type_js', 'loader_type_bundle', 'events__observable', 'events__observable_type_bem-dom', 'i-bem__collection', 'i-bem-dom', 'i-bem-dom__collection', 'i-bem-dom__init', 'i-bem-dom__init_auto', 'i-bem-dom__events', 'i-bem-dom__events_type_bem', 'i-bem-dom__events_type_dom', 'jquery__config', // desktop.blocks .js 'ua', 'jquery__event_type_winresize', ]; const missing = expectedModules.filter(m => !reg.modules.has(m)); assert.deepStrictEqual(missing, [], `Missing modules: ${missing.join(', ')}`); }); }); // --- parseDepsFile --- describe('parseDepsFile', function() { it('parses simple shouldDeps', function() { const result = parseDepsFile(resolve(ROOT, 'common.blocks/dom/dom.deps.js')); assert.ok(result); assert.ok(result.shouldDeps.length > 0); }); it('parses mustDeps', function() { const result = parseDepsFile( resolve(ROOT, 'touch.blocks/ua/__dom/ua__dom.deps.js') ); assert.ok(result); assert.ok(result.mustDeps.length > 0); }); it('parses complex deps with elem and mods', function() { const result = parseDepsFile(resolve(ROOT, 'common.blocks/i-bem-dom/i-bem-dom.deps.js')); assert.ok(result); assert.ok(result.shouldDeps.length > 0); }); it('returns null for non-existent file', function() { const result = parseDepsFile(resolve(ROOT, 'nonexistent.deps.js')); assert.strictEqual(result, null); }); }); // --- expandBemEntity --- describe('expandBemEntity', function() { it('expands string to itself', function() { assert.deepStrictEqual(expandBemEntity('jquery'), ['jquery']); }); it('expands block with elem', function() { assert.deepStrictEqual( expandBemEntity({ block: 'i-bem', elem: 'internal' }), ['i-bem__internal'] ); }); it('expands block with elems array', function() { const result = expandBemEntity({ block: 'i-bem', elems: ['internal', 'collection'] }); assert.deepStrictEqual(result, ['i-bem__internal', 'i-bem__collection']); }); it('expands elem with mods', function() { const result = expandBemEntity({ block: 'i-bem-dom', elem: 'events', mods: { type: ['dom', 'bem'] }, }); assert.deepStrictEqual(result, [ 'i-bem-dom__events_type_dom', 'i-bem-dom__events_type_bem', ]); }); it('expands block with bool mod', function() { const result = expandBemEntity({ block: 'i-bem-dom', elem: 'init', mods: { auto: true } }); assert.deepStrictEqual(result, ['i-bem-dom__init_auto']); }); it('expands block-level mods', function() { const result = expandBemEntity({ block: 'loader', mods: { type: 'js' } }); assert.deepStrictEqual(result, ['loader_type_js']); }); it('expands elems with nested mods', function() { const result = expandBemEntity({ block: 'i-bem-dom', elems: { elem: 'init', mods: { auto: true } }, }); assert.deepStrictEqual(result, ['i-bem-dom__init', 'i-bem-dom__init_auto']); }); }); // --- generateBarrel --- describe('generateBarrel', function() { it('generates chained barrel for module with redefinitions', function() { const entries = [ { filePath: resolve(ROOT, 'common.blocks/jquery/__config/jquery__config.js'), isRedefinition: false }, { filePath: resolve(ROOT, 'desktop.blocks/jquery/__config/jquery__config.js'), isRedefinition: true }, ]; const barrel = generateBarrel('jquery__config', entries, ROOT); assert.ok(barrel.includes('@generated')); assert.ok(barrel.includes("import _jquery__config_base from './common.blocks/jquery/__config/jquery__config.js'")); assert.ok(barrel.includes("import _jquery__config_redef0 from './desktop.blocks/jquery/__config/jquery__config.js'")); assert.ok(barrel.includes('let _module = _jquery__config_base;')); assert.ok(barrel.includes('_module = _jquery__config_redef0(_module);')); assert.ok(barrel.includes('export default _module;')); }); it('generates barrel for jquery__config with desktop redefinition', function() { const entries = [ { filePath: resolve(ROOT, 'common.blocks/jquery/__config/jquery__config.js'), isRedefinition: false }, { filePath: resolve(ROOT, 'desktop.blocks/jquery/__config/jquery__config.js'), isRedefinition: true }, ]; const barrel = generateBarrel('jquery__config', entries, ROOT); assert.ok(barrel.includes("import _jquery__config_base from './common.blocks/jquery/__config/jquery__config.js'")); assert.ok(barrel.includes("import _jquery__config_redef0 from './desktop.blocks/jquery/__config/jquery__config.js'")); assert.ok(barrel.includes('let _module = _jquery__config_base;')); assert.ok(barrel.includes('_module = _jquery__config_redef0(_module);')); assert.ok(barrel.includes('export default _module;')); }); it('generates barrel for events__observable with type redefinition', function() { const entries = [ { filePath: resolve(ROOT, 'common.blocks/events/__observable/events__observable.js'), isRedefinition: false }, { filePath: resolve(ROOT, 'common.blocks/events/__observable/_type/events__observable_type_bem-dom.js'), isRedefinition: true }, ]; const barrel = generateBarrel('events__observable', entries, ROOT); assert.ok(barrel.includes('_events__observable_base')); assert.ok(barrel.includes('_events__observable_redef0')); assert.ok(barrel.includes('_module = _events__observable_redef0(_module);')); }); it('barrel does NOT use side-effect imports', function() { const entries = [ { filePath: resolve(ROOT, 'common.blocks/jquery/__config/jquery__config.js'), isRedefinition: false }, { filePath: resolve(ROOT, 'desktop.blocks/jquery/__config/jquery__config.js'), isRedefinition: true }, ]; const barrel = generateBarrel('jquery__config', entries, ROOT); const sideEffectImport = /^import\s+'/m; assert.ok(!sideEffectImport.test(barrel), 'barrel should not contain side-effect imports'); }); it('barrel with single redefinition produces correct chain', function() { const entries = [ { filePath: '/root/common.blocks/foo/foo.js', isRedefinition: false }, { filePath: '/root/desktop.blocks/foo/foo.js', isRedefinition: true }, ]; const barrel = generateBarrel('foo', entries, '/root'); const expectedLines = [ "// @generated by vite-plugin-bem-levels", "import _foo_base from './common.blocks/foo/foo.js';", "import _foo_redef0 from './desktop.blocks/foo/foo.js';", "", "let _module = _foo_base;", "_module = _foo_redef0(_module);", "export default _module;", ]; assert.strictEqual(barrel, expectedLines.join('\n')); }); it('barrel with 4 redefinitions produces full chain', function() { const entries = [ { filePath: '/root/a/m.js' }, { filePath: '/root/b/m.js' }, { filePath: '/root/c/m.js' }, { filePath: '/root/d/m.js' }, { filePath: '/root/e/m.js' }, ]; const barrel = generateBarrel('m', entries, '/root'); assert.ok(barrel.includes('_m_redef0')); assert.ok(barrel.includes('_m_redef1')); assert.ok(barrel.includes('_m_redef2')); assert.ok(barrel.includes('_m_redef3')); assert.strictEqual((barrel.match(/_module = _m_redef/g) || []).length, 4); }); }); // --- safeIdentifier --- describe('safeIdentifier', function() { it('converts simple name', function() { assert.strictEqual(safeIdentifier('jquery'), '_jquery'); }); it('converts hyphenated name', function() { assert.strictEqual(safeIdentifier('i-bem-dom'), '_iBemDom'); }); it('converts name with double underscore', function() { assert.strictEqual(safeIdentifier('jquery__config'), '_jquery__config'); }); }); ================================================ FILE: build/vite.config.js ================================================ import { defineConfig } from 'vite'; import { resolve } from 'node:path'; import bemLevels from './plugins/vite-plugin-bem-levels.js'; const rootDir = resolve(import.meta.dirname, '..'); export default defineConfig(({ mode }) => { const platform = process.env.BEM_PLATFORM || 'desktop'; return { root: rootDir, plugins: [ bemLevels({ platform, levels: { common: ['common.blocks'], desktop: ['common.blocks', 'desktop.blocks'], touch: ['common.blocks', 'touch.blocks'], }, rootDir, }), ], build: { lib: { entry: resolve(import.meta.dirname, 'platforms', `${platform}.js`), name: 'bemCore', formats: ['es', 'umd'], fileName: (format) => `bem-core.${format === 'es' ? 'mjs' : 'js'}`, }, outDir: resolve(rootDir, 'dist', platform), emptyOutDir: true, sourcemap: true, minify: mode === 'production', rolldownOptions: { external: ['jquery'], output: { globals: { jquery: 'jQuery', }, }, }, }, server: { open: false, }, }; }); ================================================ FILE: build/vite.test.config.js ================================================ import { defineConfig } from 'vite'; import { resolve } from 'node:path'; import bemLevels from './plugins/vite-plugin-bem-levels.js'; const rootDir = resolve(import.meta.dirname, '..'); export default defineConfig({ root: rootDir, plugins: [ bemLevels({ // Use desktop platform for browser tests platform: 'desktop', levels: { common: ['common.blocks'], desktop: ['common.blocks', 'desktop.blocks'], touch: ['common.blocks', 'touch.blocks'], }, rootDir, }), ], // NOTE: jQuery is NOT external here — it must be bundled into the test page. // (In the production build it's a peerDependency / external.) resolve: { alias: {}, }, server: { port: 5174, open: false, }, // Polyfill Node.js globals used by mocha's browser-entry.js define: { 'process.env.NODE_ENV': JSON.stringify('test'), 'process.env': JSON.stringify({ NODE_ENV: 'test' }), 'process.stdout': 'null', 'process.version': JSON.stringify('v22.0.0'), global: 'globalThis', }, optimizeDeps: { include: ['chai', 'sinon', 'sinon-chai', 'jquery'], }, }); ================================================ FILE: common.blocks/clearfix/clearfix.css ================================================ .clearfix:after { display: table; clear: both; content: ''; } ================================================ FILE: common.blocks/clearfix/clearfix.en.md ================================================ # clearfix This block provides a CSS class that implements the **clearfix** layout hack, also known as the [Easy Clearing Method](http://www.456bereastreet.com/archive/200603/new_clearing_method_needed_for_ie7/). The hack allows you to clear wrapping for elements with the CSS `float` property, without making any changes to the document's original HTML structure. This block can be used as a container for elements with the `float` property, or mixed with such a container. Example when used as a container: ```bemjson [{ block : 'header', attrs : { style : 'border : 2px solid blue;' }, content : 'Top element' }, { block : 'clearfix', attrs : { style : 'border : 2px dotted yellow;' }, content : [ { block : 'float', attrs : { style : 'float : left; border : 1px solid green;' }, content : 'Floating item 1' }, { block : 'float', attrs : { style : 'float : left; border : 1px solid green;' }, content : 'Floating item 2' }] }, { block : 'footer', attrs : { style : 'border : 2px solid red' }, content : 'Footer' }] ``` Mixed with a container block: ```bemjson [{ block : 'header', attrs : { style : 'border : 2px solid blue;' }, content : 'Top element' }, { block : 'some-container', mix : [{ block : 'clearfix' }], attrs : { style : 'border : 2px dotted yellow;' }, content : [ { block : 'float', attrs : { style : 'float : left; border : 1px solid green;' }, content : 'Floating item 1' }, { block : 'float', attrs : { style : 'float : left; border : 1px solid green;' }, content : 'Floating item 2' }] }, { block : 'footer', attrs : { style : 'border : 2px solid red' }, content : 'Footer' }] ``` ## Public block technologies The block is implemented in: * `css`. ================================================ FILE: common.blocks/clearfix/clearfix.en.title.txt ================================================ Block with floats inside ================================================ FILE: common.blocks/clearfix/clearfix.ru.md ================================================ # clearfix Блок предоставляет СSS-класс, реализующий прием верстки **clearfix**, также известный как [Easy Clearing Hack](http://www.456bereastreet.com/archive/200603/new_clearing_method_needed_for_ie7/). Прием позволяет отменить обтекание для элементов с CSS-свойством `float`, без внесения изменений в исходную HTML-структуру документа. Блок можно использовать в качестве контейнера для элементов со свойством `float`, или примешивая его к такому контейнеру. Пример использования в качестве контейнера: ```bemjson [{ block : 'header', attrs : { style : 'border : 2px solid blue;' }, content : 'Top element' }, { block : 'clearfix', attrs : { style : 'border : 2px dotted yellow;' }, content : [ { block : 'float', attrs : { style : 'float : left; border : 1px solid green;' }, content : 'Floating item 1' }, { block : 'float', attrs : { style : 'float : left; border : 1px solid green;' }, content : 'Floating item 2' }] }, { block : 'footer', attrs : { style : 'border : 2px solid red' }, content : 'Footer' }] ``` Примешивание к блоку-контейнеру: ```bemjson [{ block : 'header', attrs : { style : 'border : 2px solid blue;' }, content : 'Top element' }, { block : 'some-container', mix : [{ block : 'clearfix' }], attrs : { style : 'border : 2px dotted yellow;' }, content : [ { block : 'float', attrs : { style : 'float : left; border : 1px solid green;' }, content : 'Floating item 1' }, { block : 'float', attrs : { style : 'float : left; border : 1px solid green;' }, content : 'Floating item 2' }] }, { block : 'footer', attrs : { style : 'border : 2px solid red' }, content : 'Footer' }] ``` ## Публичные технологии блока Блок реализован в технологиях: * `css`. ================================================ FILE: common.blocks/clearfix/clearfix.ru.title.txt ================================================ Блок с float элементами внутри ================================================ FILE: common.blocks/cookie/cookie.en.md ================================================ # cookie This block provides an object with a set of methods for working with browser cookies (the JS `document.cookie` property). ## Overview ### Object properties and methods | Name | Return type | Description | | -------- | --- | -------- | | get(`name`) | `String` | `null` | Gets the value stored in a browser cookie. | | set(`name`, `val`, `[options]`) | `String` | Sets the cookie with the specified name.| ### Public block technologies The block is implemented in: * `js` ## Description ### Object properties and methods #### `get` method Use this method to get the value stored in a cookie for the name passed in the argument. **Accepted arguments:** | Argument | Type | Description | | ------- | --- | -------- | | `name`* | `String` | The name of the cookie. | * Required argument. **Returns:** * `String` — If a cookie with the specified name was set. The value is automatically decoded using [decodeURIComponent](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent). * `null` — If a cookie with the specified name doesn't exist. Example: ```js modules.require('cookie', function(cookie) { cookie.set('mycookie', 'foobar'); console.log(cookie.get('mycookie')); // 'foobar' console.log(cookie.get('foo')); // null }); ``` #### `set` method Use this method to set the cookie with the specified name. In addition to the name and value, you can pass the method a hash with additional cookie parameters. **Accepted arguments:** | Argument | Type | Description | | ------- | --- | -------- | | `name`* | `String` | The name of the cookie. | | `val`* | `String` | `null` | The value of the cookie. If the value is set to `null`, the cookie is deleted.| | [`options`] | `Object` | Options.

Object properties

• `expires` (`Number`) – The cookie's time to live, in days. If the value is negative, the cookie is deleted. Alternatively, you can pass a generated date object (`new Date()`) for the value.
• `path` (`String`) – The path from the domain root where the cookie will be available.
• `domain` (`String`) – The domain. By default, this is the current domain.
• `secure` (`Boolean`) – Flag indicating that an encrypted SSL connection must be used with the cookie. By default, it is `false`. | * Required argument. **Returns:** the `this` object. Example: ```js modules.require('cookie', function(cookie) { cookie.set('mycookie', 'foobar', { expires : 1, // lifetime is one day path : '/', // available for all pages secure secure : true // only send the cookie over SSL }); console.log(cookie.get('mycookie')); // 'foobar' cookie.set('mycookie', null); // deleting the cookie console.log(cookie.get('mycookie')); // null }); ``` ================================================ FILE: common.blocks/cookie/cookie.js ================================================ /** * @module cookie * @description Inspired from $.cookie plugin by Klaus Hartl (stilbuero.de) */ export default { /** * Returns cookie by given name * @param {String} name * @returns {String|null} */ get(name) { let res = null if(document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';') for(let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim() // Does this cookie string begin with the name we want? if(cookie.substring(0, name.length + 1) === (name + '=')) { res = decodeURIComponent(cookie.substring(name.length + 1)) break } } } return res }, /** * Sets cookie by given name * @param {String} name * @param {String} val * @param {Object} options * @returns {cookie} this */ set(name, val, options) { options = options || {} if(val === null) { val = '' options.expires = -1 } let expires = '' if(options.expires && (typeof options.expires === 'number' || options.expires.toUTCString)) { let date if(typeof options.expires === 'number') { date = new Date() date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)) } else { date = options.expires } expires = '; expires=' + date.toUTCString() // use expires attribute, max-age is not supported by IE } // CAUTION: Needed to parenthesize options.path and options.domain // in the following expressions, otherwise they evaluate to undefined // in the packed version for some reason... const path = options.path? '; path=' + (options.path) : '', domain = options.domain? '; domain=' + (options.domain) : '', secure = options.secure? '; secure' : '' document.cookie = [name, '=', encodeURIComponent(val), expires, path, domain, secure].join('') return this } } ================================================ FILE: common.blocks/cookie/cookie.ru.md ================================================ # cookie Блок предоставляет объект, содержащий набор методов для работы с cookie браузера (JS-свойство `document.cookie`). ## Обзор ### Свойства и методы объекта | Имя | Тип возвращаемого значения | Описание | | -------- | --- | -------- | | get(`name`) | `String` | `null` | Служит для получения значения, хранящегося в cookie браузера. | | set(`name`, `val`, `[options]`) | `String` | Cлужит для записи cookie с заданным именем.| ### Публичные технологии блока Блок реализован в технологиях: * `js` ## Описание ### Свойства и методы объекта #### Метод `get` Метод служит для получения значения, хранящегося в cookie, для имени переданного аргументом. **Принимаемые аргументы:** | Аргумент | Тип | Описание | | ------- | --- | -------- | | `name`* | `String` | Имя cookie. | * Обязательный аргумент. **Возвращает:** * `String` — если cookie с заданным именем было установлено. Значение автоматически декодируется с помощью [decodeURIComponent](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent). * `null` — если cookie с заданным именем отсутствует. Пример: ```js modules.require('cookie', function(cookie) { cookie.set('mycookie', 'foobar'); console.log(cookie.get('mycookie')); // 'foobar' console.log(cookie.get('foo')); // null }); ``` #### Метод `set` Метод служит для записи cookie с заданным именем. Помимо имени и значения, методу можно передать хеш с дополнительными параметрами cookie. **Принимаемые аргументы:** | Аргумент | Тип | Описание | | ------- | --- | -------- | | `name`* | `String` | Имя cookie. | | `val`* | `String` | `null` | Значение cookie. При установке в качестве значения `null` cookie удаляется.| | [`options`] | `Object` | Опции.

Свойства объекта:

• `expires` (`Number`) – срок жизни cookie в сутках. При отрицательном значении cookie будет удалено. Альтернативно, можно передать в качестве значения сформированный объект даты (`new Date()`).
• `path` (`String`) – путь от корня домена внутри которого будет доступно cookie.
• `domain` (`String`) – домен. По умолчанию текущий домен.
• `secure` (`Boolean`) – флаг, указывающий на необходимость использования с cookie шифрованного соединения SSL. По умолчанию `false`. | * Обязательный аргумент. **Возвращает:** объект `this`. Пример: ```js modules.require('cookie', function(cookie) { cookie.set('mycookie', 'foobar', { expires : 1, // срок жизни одни сутки path : '/', // доступно для всех страниц secure : true // передавать cookie только по SSL }); console.log(cookie.get('mycookie')); // 'foobar' cookie.set('mycookie', null); // удаляем cookie console.log(cookie.get('mycookie')); // null }); ``` ================================================ FILE: common.blocks/cookie/cookie.spec.js ================================================ modules.define('spec', [ 'cookie', 'chai' ], function(provide, cookie, chai ) { var expect = chai.expect; describe('cookie', function() { describe('get', function() { it('should return value of defined cookie', function() { document.cookie = 'name1=val1'; document.cookie = 'name2=val2'; cookie.get('name1').should.be.equal('val1'); cookie.get('name2').should.be.equal('val2'); }); it('should return null if cookie is undefined', function() { expect(cookie.get('name3')).to.be.null; }); }); describe('set', function() { it('should properly set cookie', function() { cookie.set('name', 'val'); cookie.get('name').should.be.equal('val'); }); it('should properly remove cookie', function() { cookie.set('name', 'val'); cookie.set('name', null); expect(cookie.get('name')).to.be.null; }); }); }); provide(); }); ================================================ FILE: common.blocks/dom/dom.deps.js ================================================ ({ shouldDeps : 'jquery' }) ================================================ FILE: common.blocks/dom/dom.en.md ================================================ # dom This block provides an object with a set of methods for working with the DOM tree. ## Overview ### Object properties and methods | Name | Return type | Description | | -------- | --- | -------- | | contains(
`ctx {jQuery}`,
`domElem {jQuery}`) | `Boolean` | Checks whether a DOM element contains another DOM element. | | getFocused(
`domElem {jQuery} `) | `jQuery` | Gets a reference to the DOM element that is in focus. | | containsFocus(
`domElem {jQuery} `) | `Boolean` | Checks whether a DOM element or its descendants contains the focus. | | isFocusable(
`domElem {jQuery} `) | `Boolean` | Checks whether the DOM element is in focus. | | isEditable(
`domElem {jQuery}`) | `Boolean` | Checks whether text can be entered in the DOM element. | ### Public block technologies The block is implemented in: * `js` ## Description ### Object properties and methods #### `contains` method Use this method to check whether a `ctx` DOM element contains `domElem`. **Accepted arguments:** * `ctx {jQuery}` – The DOM element to search inside. Required argument. * `domElem {jQuery}` – The DOM element to search for. Required argument. **Return value:** `Boolean`. If found, then `true`. Example: ```js modules.require(['dom', 'jquery'], function(dom, $) { /*
*/ dom.contains($('.block1'), $('.block2')); // true }); ``` #### `getFocused` method Gets a reference to the DOM element that is in focus. Doesn't accept arguments. **Return value:** `jQuery` – The object in focus. Example: ```js modules.require(['dom'], function(dom) { dom.getFocused(); // a reference to the element in focus }); ``` #### `containsFocus` method This method checks whether the focus is on the DOM element passed in the argument or one of its descendants. **Accepted arguments:** * `domElem {jQuery}` – The DOM element to check. Required argument. **Return value:** `Boolean`. If this element is in focus, then `true`. Example: ```js modules.require(['dom', 'jquery'], function(dom, $) { /*
*/ $('.block1__control').focus(); dom.containsFocus($('.block1')); // true }); ``` #### `isFocusable` method This method checks whether the user's browser can set the focus on the DOM element passed in the argument. **Accepted arguments:** * `domElem {jQuery}` – The DOM element to check. Required argument. If there are mutiple DOM elements in the jQuery chain, the first one is checked. **Return value:** `Boolean`. If the focus can be set on this element, then `true`. Example: ```js modules.require(['dom', 'jquery'], function(dom, $) { /* */ dom.isFocusable($('.menu__item')); // true /* */ dom.isFocusable($('.menu__item')); // false }); ``` #### `isEditable` method This method checks whether text can be entered in the DOM element passed in the argument. In other words, you can use this method to check whether the element is an input field, text field, and so on. **Accepted arguments:** * `domElem {jQuery}` – The DOM element to check. Required argument. If there are mutiple DOM elements in the jQuery chain, the first one is checked. **Return value:** `Boolean`. If text can be entered in the DOM element, then `true`. Example: ```js modules.require(['dom', 'jquery'], function(dom, $) { dom.isEditable($('input, textarea')); // true }); ``` ================================================ FILE: common.blocks/dom/dom.js ================================================ /** * @module dom * @description some DOM utils */ import $ from 'bem:jquery' const EDITABLE_INPUT_TYPES = new Set([ 'datetime-local', 'date', 'month', 'number', 'password', 'search', 'tel', 'text', 'time', 'url', 'week' ]) export default { /** * Checks whether a DOM elem is in a context * @param {jQuery} ctx DOM elem where check is being performed * @param {jQuery} domElem DOM elem to check * @returns {Boolean} */ contains(ctx, domElem) { let res = false domElem.each(function() { let domNode = this do { if(~ctx.index(domNode)) return !(res = true) } while(domNode = domNode.parentNode) return res }) return res }, /** * Returns current focused DOM elem in document * @returns {jQuery} */ getFocused() { // "Error: Unspecified error." in iframe in IE9 try { return $(document.activeElement) } catch(e) {} }, /** * Checks whether a DOM element contains focus * @param {jQuery} domElem * @returns {Boolean} */ containsFocus(domElem) { return this.contains(domElem, this.getFocused()) }, /** * Checks whether a browser currently can set focus on DOM elem * @param {jQuery} domElem * @returns {Boolean} */ isFocusable(domElem) { const domNode = domElem[0] if(!domNode) return false if(domNode.hasAttribute('tabindex')) return true switch(domNode.tagName.toLowerCase()) { case 'iframe': return true case 'input': case 'button': case 'textarea': case 'select': return !domNode.disabled case 'a': return !!domNode.href } return false }, /** * Checks whether a domElem is intended to edit text * @param {jQuery} domElem * @returns {Boolean} */ isEditable(domElem) { const domNode = domElem[0] if(!domNode) return false switch(domNode.tagName.toLowerCase()) { case 'input': return EDITABLE_INPUT_TYPES.has(domNode.type) && !domNode.disabled && !domNode.readOnly case 'textarea': return !domNode.disabled && !domNode.readOnly default: return domNode.contentEditable === 'true' } } } ================================================ FILE: common.blocks/dom/dom.ru.md ================================================ # dom Блок предоставляет объект, содержащий набор методов для работы с DOM-деревом. ## Обзор ### Свойства и методы объекта | Имя | Тип возвращаемого значения | Описание | | -------- | --- | -------- | | contains(
`ctx {jQuery}`,
`domElem {jQuery}`) | `Boolean` | Проверяет, содержит ли один DOM-элемент другой. | | getFocused(
`domElem {jQuery} `) | `jQuery` | Служит для получения ссылки на DOM-элемент в фокусе. | | containsFocus(
`domElem {jQuery} `) | `Boolean` | Проверят, содержит ли DOM-элемент или его потомки фокус. | | isFocusable(
`domElem {jQuery} `) | `Boolean` | Проверят, может ли DOM-элемент находиться в фокусе. | | isEditable(
`domElem {jQuery}`) | `Boolean` | Проверят, возможен ли в DOM-элементе ввод текста. | ### Публичные технологии блока Блок реализован в технологиях: * `js` ## Описание ### Свойства и методы объекта #### Метод `contains` Метод позволяет проверить содержит ли некоторый DOM-элемент `ctx` элемент `domElem`. **Принимаемые аргументы:** * `ctx {jQuery}` – DOM-элемент внутри которого производится поиск. Обязательный аргумент. * `domElem {jQuery}` – искомый DOM-элемент. Обязательный аргумент. **Возвращаемое значение:** `Boolean`. Если искомый элемент найден – `true`. Пример: ```js modules.require(['dom', 'jquery'], function(dom, $) { /*
*/ dom.contains($('.block1'), $('.block2')); // true }); ``` #### Метод `getFocused` Метод служит для получения ссылки на DOM-элемент, находящийся в фокусе. Не принимает аргументов. **Возвращаемое значение:** `jQuery` – объект в фокусе. Пример: ```js modules.require(['dom'], function(dom) { dom.getFocused(); // ссылка на элемент в фокусе }); ``` #### Метод `containsFocus` Метод проверяет находится ли в фокусе переданный аргументом DOM-элемент или один из его потомков. **Принимаемые аргументы:** * `domElem {jQuery}` – проверяемый DOM-элемент. Обязательный аргумент. **Возвращаемое значение:** `Boolean`. Если искомый элемент в фокусе – `true`. Пример: ```js modules.require(['dom', 'jquery'], function(dom, $) { /*
*/ $('.block1__control').focus(); dom.containsFocus($('.block1')); // true }); ``` #### Метод `isFocusable` Метод проверят может ли браузер пользователя установить фокус на переданный аргументом DOM-элемент. **Принимаемые аргументы:** * `domElem {jQuery}` – проверяемый DOM-элемент. Обязательный аргумент. Если в jQuery-цепочке несколько DOM-элементов, то проверяется первый из них. **Возвращаемое значение:** `Boolean`. Если фокус может быть установлен – `true`. Пример: ```js modules.require(['dom', 'jquery'], function(dom, $) { /* */ dom.isFocusable($('.menu__item')); // true /* */ dom.isFocusable($('.menu__item')); // false }); ``` #### Метод `isEditable` Метод проверят возможен ли в переданном аргументом DOM-элементе ввод текста. Другими словами, с помощью метода можно проверить является ли элемент полем ввода, текстовой областью и т.п. **Принимаемые аргументы:** * `domElem {jQuery}` – проверяемый DOM-элемент. Обязательный аргумент. Если в jQuery-цепочке несколько DOM-элементов, то проверяется первый из них. **Возвращаемое значение:** `Boolean`. Если ввод текста в элементе возможен – `true`. Пример: ```js modules.require(['dom', 'jquery'], function(dom, $) { dom.isEditable($('input, textarea')); // true }); ``` ================================================ FILE: common.blocks/dom/dom.spec.js ================================================ modules.define('spec', [ 'dom', 'jquery' ], function(provide, dom, $ ) { describe('dom', function() { describe('contains', function() { var domElem; beforeEach(function() { domElem = $( '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
') .appendTo('body'); }); afterEach(function() { domElem.remove(); }); it('should properly checks for nested dom elem', function() { dom.contains(domElem.find('.a'), domElem.find('.x')).should.be.true; dom.contains(domElem.find('.a'), domElem.find('.y')).should.be.true; dom.contains(domElem.find('.c'), domElem.find('.x')).should.be.false; }); it('should returns true for itself', function() { dom.contains(domElem.find('.x'), domElem.find('.x')).should.be.true; }); it('should returns false for empty DOM elem', function() { dom.contains(domElem.find('.a'), domElem.find('.no-exist')).should.be.false; }); }); describe('getFocused', function() { it('should returns focused DOM elem', function() { var elem = $('') .appendTo('body') .focus(); dom.getFocused()[0].should.be.eql(elem[0]); elem.blur(); dom.getFocused()[0].should.not.be.eql(elem[0]); elem.remove(); }); }); describe('isFocusable', function() { it('should returns true if given DOM elem is iframe, input, button, textarea or select', function() { dom.isFocusable($('