8102 Как написать современную библиотеку JavaScript

JavaScript Webpack Babel Открытый исходный код

я написал несколькопроект с открытым исходным кодом, У меня есть некоторый опыт работы с открытым исходным кодом. Недавно я открыл Weibo господина Жуана, и я глубоко тронут. Теперь проект с открытым исходным кодом включает в себя много вещей, особенно для новичков. Это очень недружелюбно

Недавно я написалjslib-baseРазработанная во многих отношениях, чтобы помочь вам быстро настроить стандартную библиотеку JS, эта статья будет иметь JSLIB-база в качестве примера, чтобы написать библиотеку знаний с открытым исходным кодом

jslib-base Лучший каркас сторонних библиотек js, позволяющий использовать сторонние библиотеки js с открытым исходным кодом, что делает разработку библиотеки js более простой и профессиональной

Документация

Так называемый код не перемещается, документ является первым, документ очень важен для проекта, документ проекта включает в себя

README.md

README – это фасад проекта. В нем должны быть просто и ясно изложены вопросы, которые больше всего беспокоят пользователей. К пользователям библиотеки с открытым исходным кодом относятся пользователи и участники, поэтому документ должен состоять из трех частей: введение в проект, руководство пользователя и руководство автора.

Введение в проект Используйте это, чтобы кратко представить соответствующие сведения о функциях проекта, сценариях использования и совместимости. Здесь мы сосредоточимся на значках. Я думаю, что все видели значки в проектах других людей, как показано ниже.

Значок представляет больше информации более интуитивно понятным способом, а также может улучшить внешний вид.Существует веб-сайт, посвященный созданию различных значков, вы можете увидетьздесь

Вот READMEполный пример

TODO.md

TODO должен записывать будущий план проекта, который имеет большое значение как для участников, так и для пользователей, ниже приведен пример TODO.

- [X] 已完成
- [ ] 未完成

CHANGELOG.md

CHANGELOG записывает журнал изменений проекта, что очень важно для пользователей проекта, особенно при обновлении используемой версии. CHANGELOG должен записывать версию проекта, время выпуска и запись изменения версии.

## 0.1.0 / 2018-10-6

- 新增xxx功能
- 删除xxx功能
- 更改xxx功能

LICENSE

Проекты с открытым исходным кодом должны выбирать протокол, потому что никто не осмеливается использовать проект без протокола.Разницу между различными протоколами вы можете увидеть на следующем рисунке (из блога г-на Руана).Мое предложение - выбрать протокол MIT или BSD.

doc

Проекты с открытым исходным кодом также должны предоставлять подробную документацию по использованию, и описание каждой функции в подробной документации должно включать следующую информацию:

函数简单介绍

函数详细介绍

函数参数和返回值(要遵守下面的例子的规则)

- param {string} name1 name1描述
- return {string} 返回值描述

举个例子(要包含代码用例)

// 代码

特殊说明,比如特殊情况下会报错等

Построить

Идеальная ситуация выглядит следующим образом:

  • Разработчик библиотеки с удовольствием пишет код ES6+;
  • Потребители библиотеки могут работать в браузере (т.е. 6-11) и узле (0.12-10)
  • Пользователи библиотеки могут использовать схемы модулей AMD или CMD.
  • Пользователи библиотеки могут использовать прекомпиляторы, такие как webpack, rollup или fis.

В идеале полно, в реальности очень. . . , как разработчики и пользователи могут быть счастливы?jslib-base предоставляет решение через babel+rollup

компилировать

С помощью babel код ES6+ может быть скомпилирован в код ES5.

В этой статье не обсуждается эволюционная история Babel (отдельная запись в блоге будет представлена ​​позже), а выбираются самые современныеbabel-preset-envСхема babel-preset-env может решать, какие функции ES компилировать, предоставляя совместимую среду.

Принцип примерно такой: во-первых, через характеристики ЭС иСписок совместимых функцийРассчитайте информацию о совместимости каждой функции, а затем рассчитайте подключаемый модуль babel, который будет использоваться, указав требования совместимости.

Сначала нужно установитьbabel-preset-env

$ npm i --save-dev babel-preset-env

Затем добавьте новый файл .babelrc и добавьте следующее содержимое.

{
  "presets": [
    ["env",
    {
      "targets": {
        "browsers": "last 2 versions, > 1%, ie >= 6, Android >= 4, iOS >= 6, and_uc > 9",
        "node": "0.10"
      },
      "modules": false,
      "loose": false
    }]
  ]
}

targetsКонфигурация должна быть совместима со средой. Список браузеров, соответствующих конфигурации браузера, можно найти на страницеbrowserl.istСмотреть на

modulesУказывает тип выходного модуля, поддерживает «amd», «umd», «systemjs», «commonjs», false для этих параметров, false означает, что тип модуля не выводится

looseПредставляет свободный режим, для которого установлено значение true, который лучше совместим со средами ниже ie8. Ниже приведен пример (ie8 не поддерживаетObject.defineProperty)

// 源代码
const aaa = 1;
export default aaa;


// loose false
Object.defineProperty(exports, '__esModule', {
    value: true
});
var aaa = 1;
exports.default = 1;


// loose true
exports.__esModule = true;
var aaa = 1;
exports.default = 1;

babel-preset-envОн решает проблему совместимости новых возможностей грамматики.Если вы хотите использовать новые возможности API, это обычно решается с помощью babel-polyfill в babel.Babel-polyfill решает проблему введением файла полифилла, который очень удобно для обычных проектов, но для библиотек не очень удобно

Решение, предоставляемое babel разработчикам библиотек, таково:babel-transform-runtime, среда выполнения предоставляет аналогичную среду выполнения программы, которая может выполнять изолированную программную среду для глобального полифилла.

Сначала нужно установитьbabel-transform-runtime

$ npm i --save-dev babel-plugin-transform-runtime

Добавьте следующую конфигурацию в .babelrc

"plugins": [
  ["transform-runtime", {
    "helpers": false,
    "polyfill": false,
    "regenerator": false,
    "moduleName": "babel-runtime"
  }]
]

transform-runtime, поддерживает три среды выполнения, ниже приведен пример полифилла

// 源代码
var a = Promise.resolve(1);

// 编译后的代码
var _promise = require('babel-runtime/core-js/promise');

var a = _promise.resolve(1); // Promise被替换为_promise

Хотя проблему можно решить элегантно, импортируемый файл очень большой.Например, если используется только функция поиска массива в ES6, может быть введен код в несколько тысяч строк.Мое предложение - лучше не использовать его для библиотеки.

Пакет

Компиляция решает проблему от ES6 до ES5.Упаковка может объединять несколько файлов в один файл и предоставлять унифицированную запись файла для внешнего мира.Упаковка решает проблему введения зависимостей.

rollup vs webpack

В качестве инструмента для упаковки я выбрал Rollup. Rollup известен как решение для упаковки следующего поколения. Он имеет следующие функции.

  • Анализ зависимостей, сборка пакетов
  • Поддерживаются только модули ES6.
  • Tree shaking

Webpack — самое популярное решение для упаковки, а rollup — решение для упаковки следующего поколения.На самом деле, разницу между ними можно выразить одним предложением: в библиотеке используется rollup, а в других сценариях — webpack.

Почему я так говорю? Давайте сравним разницу между webpack и rollup на примере

Предположим, у нас есть два файла index.js и bar.js со следующим кодом.

ливневая канализация bar.js вне функцииbar

export default function bar() {
  console.log('bar')
}

index.js ссылается на bar.js

import bar from './bar';

bar()

Ниже приведен файл конфигурации веб-пакета webpack.config.js.

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    }
};

Давайте взглянем на содержимое вывода упаковки webpack, о(╯□╰)о, не волнуйтесь, наш код находится в нескольких нижних строках, большая часть кода выше на самом деле представляет собой простую модульную систему, сгенерированную webpack, проблема решения веб-пакета заключается в том, что он будет генерировать много избыточного кода, что не является проблемой для бизнес-кода, но не очень удобно для библиотек.

Примечание. Следующий код основан на webpack3, в webpack4 добавлено поднятие области действия и несколько модулей объединены в анонимную функцию.

/******/
(function(modules) { // webpackBootstrap
    /******/ // The module cache
    /******/
    var installedModules = {};
    /******/
    /******/ // The require function
    /******/
    function __webpack_require__(moduleId) {
        /******/
        /******/ // Check if module is in cache
        /******/
        if (installedModules[moduleId]) {
            /******/
            return installedModules[moduleId].exports;
            /******/
        }
        /******/ // Create a new module (and put it into the cache)
        /******/
        var module = installedModules[moduleId] = {
            /******/
            i: moduleId,
            /******/
            l: false,
            /******/
            exports: {}
            /******/
        };
        /******/
        /******/ // Execute the module function
        /******/
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        /******/
        /******/ // Flag the module as loaded
        /******/
        module.l = true;
        /******/
        /******/ // Return the exports of the module
        /******/
        return module.exports;
        /******/
    }
    /******/
    /******/
    /******/ // expose the modules object (__webpack_modules__)
    /******/
    __webpack_require__.m = modules;
    /******/
    /******/ // expose the module cache
    /******/
    __webpack_require__.c = installedModules;
    /******/
    /******/ // define getter function for harmony exports
    /******/
    __webpack_require__.d = function(exports, name, getter) {
        /******/
        if (!__webpack_require__.o(exports, name)) {
            /******/
            Object.defineProperty(exports, name, {
                /******/
                configurable: false,
                /******/
                enumerable: true,
                /******/
                get: getter
                /******/
            });
            /******/
        }
        /******/
    };
    /******/
    /******/ // getDefaultExport function for compatibility with non-harmony modules
    /******/
    __webpack_require__.n = function(module) {
        /******/
        var getter = module && module.__esModule ?
            /******/
            function getDefault() { return module['default']; } :
            /******/
            function getModuleExports() { return module; };
        /******/
        __webpack_require__.d(getter, 'a', getter);
        /******/
        return getter;
        /******/
    };
    /******/
    /******/ // Object.prototype.hasOwnProperty.call
    /******/
    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
    /******/
    /******/ // __webpack_public_path__
    /******/
    __webpack_require__.p = "";
    /******/
    /******/ // Load entry module and return exports
    /******/
    return __webpack_require__(__webpack_require__.s = 0);
    /******/
})
/************************************************************************/
/******/
([
    /* 0 */
    /***/
    (function(module, __webpack_exports__, __webpack_require__) {

        "use strict";
        Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
        /* harmony import */
        var __WEBPACK_IMPORTED_MODULE_0__bar__ = __webpack_require__(1);


        Object(__WEBPACK_IMPORTED_MODULE_0__bar__["a" /* default */ ])()


        /***/
    }),
    /* 1 */
    /***/
    (function(module, __webpack_exports__, __webpack_require__) {

        "use strict";
        /* harmony export (immutable) */
        __webpack_exports__["a"] = bar;

        function bar() {
            //
            console.log('bar')
        }


        /***/
    })
    /******/
]);

Посмотрим на результаты роллапа, конфигурация роллапа аналогична webpack

export default {
    input: 'src/index.js',
    output: {
        file: 'dist/bundle2.js',
        format: 'cjs'
    }
};

Давайте взглянем на вывод rollup. Он идеален. Модуль полностью исчез. Rollup решает проблему зависимости модулей, последовательно вводя его в один и тот же файл. модуль полностью прозрачен, но это идеальное решение для разработчиков библиотек

'use strict';

function bar() {
  //
  console.log('bar');
}

bar();

Модульное решение

До модуляризации ES6 сообщество JS исследовало некоторые модульные системы, такие как commonjs в узле, AMD в браузерах и UMD, которые могут быть одновременно совместимы с разными модульными системами.Если вам интересна эта часть, вы можете прочитать мой предыдущий Статья из "Прошлое и настоящее модулей JavaScript

Для встроенных в браузер предварительно скомпилированных инструментов и узла схема модуляции в разных средах также отличается; поскольку среда браузера не может разрешать сторонние зависимости, среда браузера также должна упаковывать зависимости; файлы, на которые ссылаются в разных средах, не совпадают. , ниже таблица для сравнения

Браузер (скрипт, AMD, CMD) Инструменты прекомпилятора (webpack, rollup, fis) Node
Справочные материалы index.aio.js index.esm.js index.js
Модульное решение UMD ES Module commonjs
самостоятельность Пакет Пакет Пакет
сторонние зависимости Пакет не упаковано не упаковано

Примечание. Модульная система в устаревшем режиме совместима с ie6-8, но из-за одного изbug(Я нашел эту ошибку, но накопитель не планирует ее исправлять, ╮(╯▽╰)╭), в устаревшем режиме экспорт и экспорт по умолчанию нельзя использовать одновременно

tree shaking

Rollup естественным образом поддерживает встряхивание дерева, которое может предлагать неиспользуемые части зависимых модулей, что очень полезно для сторонних зависимостей и может значительно уменьшить размер пакета.

Например, предположим, что index.js просто использует функцию из стороннего пакета is.js.isStringTreeshaking не произойдет is.js все ссылки

Если используется treeshaking, другие функции в is.js могут быть исключены, и толькоisStringфункция

Технические характеристики

Здесь нет правил и нет радиуса, особенно для проектов с открытым исходным кодом, потому что будет задействовано много людей, поэтому каждый получит в два раза больше результата с половиной усилий, следуя стандарту.

Характеристики редактора

сначала по.editorconfigЧтобы обеспечить согласованность отступов и разрывов строк, большинство браузеров в настоящее время поддерживают это, вы можете увидетьздесь

Следующие параметры конфигурации заменяют вкладку пробелами в js, css и html, вкладка - это 4 пробела, используют разрывы строк unix, используют набор символов utf8 и добавляют пустую строку в конец каждого файла.

root = true

[{*.js,*.css,*.html}]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
insert_final_newline = true

[{package.json,.*rc,*.yml}]
indent_style = space
indent_size = 2

стиль кода

Во-вторых, eslint можно использовать для обеспечения того же стиля кода.Установка и настройка eslint здесь не будет объясняться.В базе jslib вам нужно только запустить следующую команду для проверки кода.Файл конфигурации eslint находится вconfig/.eslintrc.js

$ npm run lint

Спецификация проекта

Eslint может гарантировать только спецификации кода, но не может гарантировать отличный дизайн интерфейса.Существуют некоторые рекомендации по функциональному дизайну интерфейса.

Количество параметров

  • Количество параметров функции не должно превышать 5

необязательный параметр

  • Необязательные параметры следует размещать после
  • Когда количество необязательных параметров превышает три, вы можете использовать объект для передачи
  • необязательный параметр, должно быть указано значение по умолчанию

Проверка параметров и преобразование типов

  • Обязательные параметры, если не передать, будет сообщено об ошибке
  • Обязательные проверки требуются для следующих типов, при этом будет выдано сообщение об ошибке, если тип неверный (объект, массив, функция)
  • Выполнять автоматические преобразования для следующих типов (число, строка, логическое значение)
  • Для внутренних данных составных типов также выполните два вышеуказанных шага.
  • Если число равно NaN после преобразования, необходимо выполнить специальную обработку (значение по умолчанию назначается как значение по умолчанию, и сообщается об ошибке, если значение по умолчанию отсутствует)

Тип параметра

  • Максимально используйте типы значений для параметров (простые типы)
  • Старайтесь не использовать сложные типы для параметров (во избежание побочных эффектов).
  • При использовании сложных типов не углубляйтесь
  • При работе со сложными типами данных следует делать глубокую копию (во избежание побочных эффектов)

возвращаемое значение функции

  • Возвращаемое значение может вернуть результат операции (получить интерфейс), прошла ли операция успешно (сохранить интерфейс)

  • Тип возвращаемого значения должен быть согласованным

  • Возвращаемое значение должно максимально использовать тип значения (простой тип).

  • Старайтесь не использовать сложные типы для возвращаемых значений (во избежание побочных эффектов)

спецификация версии

Версия должна соответствовать общему сообществу открытого исходного кодаСемантическое управление версиями

Формат номера версии: x.y.z

  • x основной номер версии, несовместимые изменения
  • y дополнительный номер версии, совместимые изменения
  • z номер версии, исправления ошибок

Спецификация коммита Git

Представление кода должно соответствовать спецификации, вот рекомендуемыймоя норма

тестовое задание

Библиотеки без модульных тестов — это хулиганы. Модульные тесты могут гарантировать, что каждая поставка будет гарантированно качественной. требования к качеству чрезвычайно высоки, поэтому модульное тестирование необходимо

Технических решений для юнит-тестирования много, одним из вариантов являетсяmocha+chai, mocha — это среда модульного тестирования, используемая для организации, запуска модульных тестов и вывода отчетов о тестировании; chai — это библиотека утверждений, используемая для выполнения функции утверждений модульного тестирования.

Поскольку Chai не совместимо с IE6-8, выбрана другая библиотека утвержденияexpect.jsОжидайте, что BDD - это библиотека утверждения, совместимость очень хорошая, поэтому я выбрал Mocha + Exced.js

Различия между BDD и TDD здесь повторяться не будут, заинтересованные студенты могут проверить соответствующую информацию самостоятельно.

С тестовой средой вам также необходимо написать код модульного теста, пример ниже.

var expect = require('expect.js');

var base = require('../dist/index.js');

describe('单元测试', function() {
    describe('功能1', function() {
        it('相等', function() {
            expect(1).to.equal(1);
        });
    });
});

Затем просто запустите следующую команду, mocha автоматически запустит файл js в тестовом каталоге.

$ mocha

mocha поддерживает тестирование в узле и браузерах, но у вышеупомянутого фреймворка есть проблема в браузерах, и браузеры не могут его поддерживатьrequire('expect.js'), я использовал более хакерский метод для решения проблемы, переопределив значение require в раннем браузере

<script src="../../node_modules/mocha/mocha.js"></script>
<script src="../../node_modules/expect.js/index.js"></script>
<script>
    var libs = {
        'expect.js': expect,
        '../dist/index.js': jslib_base
    };
    var require = function(path) {
        return libs[path];
    }
</script>

Ниже приведен пример создания тестового отчета с помощью mocha, слева — в узле, справа — в браузере.

устойчивая интеграция

Библиотеки без устойчивой интеграции — примитивные люди.Было бы неплохо, если бы модульные тесты могли запускаться автоматически каждый раз, когда вы нажимаете, что избавило бы вас от утомительного ручного запуска.К счастью,travis-ciуже предоставляет нам эту функциональность

Войдите в travis-ci с GitHub, вы увидите свой проект на GitHub, а затем вам нужно включить переключатель проекта, чтобы включить функцию автоматической интеграции.

На втором шаге также необходимо добавить файл в проект.travis.yml, содержимое выглядит следующим образом, так что он может автоматически запускаться под версией node 4 6 8 каждый раз, когда вы нажимаетеnpm testкоманда, чтобы достичь цели автоматического тестирования

language: node_js
node_js:
  - "8"
  - "6"
  - "4"

Другой контент

Библиотека с открытым исходным кодом надеется получить обратную связь от пользователей. Если есть требование по проблеме, поднятой пользователем, можно установить шаблон для стандартизации проблемы, о которой сообщает пользователь на github. Некоторая информация должна быть сформулирована

путем предоставления.github/ISSUE_TEMPLATEФайл может предоставить шаблон для проблемы. Ниже приведен пример. Когда пользователь отправляет проблему, следующая информация о подсказке будет автоматически доставлена

### 问题是什么
问题的具体描述,尽量详细

### 环境
- 手机: 小米6
- 系统:安卓7.1.1
- 浏览器:chrome 61
- jslib-base版本:0.2.0
- 其他版本信息

### 在线例子
如果有请提供在线例子

### 其他
其他信息

jsmini

jsminiЭто серия библиотек, основанных на базе jslib.Концепция jsmini заключается в том, что она маленькая, красивая и не имеет сторонних зависимостей.Он имеет множество возможностей с открытым исходным кодом и может Помогите разработчикам библиотек

Суммировать

Эта статья за пять лет обобщает мой собственный опыт ведения проектов с открытым исходным кодом. Надеюсь, она сможет вам помочь. Весь представленный контент можно найти вjslib-baseнайден внутри

jslib-base — это готовый к использованию каркас, который позволяет использовать сторонние библиотеки js с открытым исходным кодом и быстро открывать исходный код стандартной библиотеки js.

Наконец, позвольте мне дать вам слово, проект с открытым исходным кодом,重在开始,贵在坚持

Наконец, я рекомендую мою новую книгу «React State Management and Isomorphic Practice», глубокую интерпретацию передовой изоморфной технологии, спасибо за вашу поддержку.

Цзиндон:item.JD.com/12403508.Контракт…

Данданг:product.dangdang.com/25308679.Контракт…

Наконец-то мы наконец-то набираем фронтенд, бэкенд и клиентскую часть! Расположение: Пекин + Шанхай + Чэнду, заинтересованные студенты могут отправить свои резюме на мой почтовый ящик:yanhaijing@yeah.net

Исходный URL:Yanhaijing.com/JavaScript/…