I have written some open source projects and have some experience in open source. Recently, I opened Teacher Ruan’s micro blog and deeply felt that an open source project involves a lot of things, especially unfriendly for beginners

Recently I wrote a jslib-Base, aims to help you quickly build a standard JS library from many aspects, this article will have jslib-base as an example, introduces the knowledge of writing an open source library

Jslib-base is the best use of JS third-party library scaffolding, enabling third-party JS library open source, so that the development of a JS library is simpler, more professional

The document

The so-called code does not move, documentation first, documentation for a project is very important, a project documentation includes

  • README.md
  • TODO.md
  • CHANGELOG.md
  • LICENSE
  • doc

README.md

The README is the facade of a project and should be a straightforward presentation of the issues that users are most concerned about. An open source library’s users include users and contributors, so a document should include a project profile, a user guide, and a contributor guide

Project introduction With this brief introduction of the project functions, use scenarios, compatibility of the relevant knowledge, here focuses on the badge, I believe you have seen the badge in other projects, as shown below

Badges provide more information in a more visual way and can also improve your appearance. There is a website that makes badges, which you can check out here

Here’s a complete example of README

TODO.md

TODO should document the project’s future plans, which are important to both contributors and users. Here is an example of TODO

- [X] Completed - [] Not completedCopy the code

CHANGELOG.md

CHANGELOG records project change logs, which are very important for project users. In particular, CHANGELOG records the version, release time, and version change of the project

# # 0.1.0 from / 2018-10-6- Add XXX function - Delete XXX function - Change XXX functionCopy the code

LICENSE

Open source projects must choose a protocol, because no one will dare to use a project without a protocol. For the difference between different protocols, please see the following chart (from Ruan’s blog). My suggestion is to choose MIT or BSD protocol

doc

Open source projects should also provide detailed usage documentation. Each function description in a detailed documentation should include the following information:

- param {string} name1 Name1 description - Param {string} name1 description - param {string} name1 description -return{string} Return value description for example (to include code use cases) // code special description, such as special cases will report errors, etcCopy the code

build

The ideal situation is as follows:

  • Library developers happily write ES6+ code;
  • Library users can run in browsers (IE6-11) and Node (0.12-10)
  • Library users can use AMD or CMD module schemes
  • Library users can use precompilation tools such as WebPack, rollup, or FIS

Ideal is full, reality is very… Jslib-base provides a Babel +rollup solution to make both developers and users happy

compile

Babel allows ES6+ code to be compiled into ES5 code. Babel has evolved from 5 to 6. The following chart summarizes the changes in the way Babel is used

Instead of discussing the evolution of Babel (a separate blog post will follow), this article chooses the most modern babel-Preset -env scheme, which determines which ES features are preset by providing a compatible environment

The principle is roughly as follows: first, the compatibility information of each feature is calculated based on the features of ES and the compatibility list of features. Then, the Babel plug-in to be used is calculated based on the compatibility requirements

Babel-preset -env needs to be installed first

$ npm i --save-dev babel-preset-env
Copy the code

Then add a.babelrc file and add the following

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

Targets requires compatible environments to be configured. You can view the browser list on Browser. ist

Modules specifies the module type to output. Options such as “AMD “,” UMD “,” systemJS “,” commonJS “,false are supported. False indicates that no module type is output

Loose stands for loose mode. Setting loose to true makes it more compatible with ie8 environments. Here is an example (IE8 does not support Object.defineProperty)

/ / the source code
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;
Copy the code

Babel-polyfill fixes the problem of compatibility with new syntactic features. If you want to use API features, babel-polyfill is used in Babel. Babel-polyfill fixes the problem by introducing a polyfill file, which is useful for common projects. But not so friendly for Ku

The Babel solution for library developers is babel-transform-Runtime, which provides a similar program to sandbox global polyfills when run

First you need to install babel-transform-Runtime

$ npm i --save-dev babel-plugin-transform-runtime
Copy the code

Add the following configuration to.babelrc

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

Transform-runtime supports three runtimes. Here is an example of polyfill

/ / the source code
var a = Promise.resolve(1);

// The compiled code
var _promise = require('babel-runtime/core-js/promise');

var a = _promise.resolve(1); // Promise is replaced with _PROMISE
Copy the code

While this is an elegant solution, the size of the file introduced is very large. For example, using only ES6’s find function for arrays can introduce thousands of lines of code

packaging

Compilation solves ES6 to ES5 problems, packaging can merge multiple files into a single file, providing a unified file entry externally, packaging solves the problem of dependency introduction

rollup vs webpack

I chose Rollup as the packaging tool. Rollup is billed as the next generation packaging solution and has the following features

  • Dependency resolution, package build
  • Only ES6 modules are supported
  • Tree shaking

Webpack as the most popular packaging solution and Rollup as the next generation packaging solution can be summed up in one sentence: libraries use Rollup and other scenarios use WebPack

Why do I say that? Here’s a comparison of webPack and rollup examples

Suppose we have two files, index.js and bar.js, with the following code

Bar.js exposes a function bar

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

Index. Js reference bar. Js

import bar from './bar';

bar()
Copy the code

Below is the webpack configuration file webpack.config.js

const path = require('path');

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

Now let’s take a look at the output of WebPack, o(╯□╰ o), don’t worry, our code is in the bottom few lines, this is a simple module system generated by WebPack, the problem with WebPack solution is that it generates a lot of redundant code, which is fine for business code, But not so friendly for Ku

Note: The following code is based on Webpackage 3, which adds scope reactive, which has merged multiple modules into an anonymous function

/ * * * * * * /
(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')}/ * * * /
    })
    / * * * * * * /
]);
Copy the code

Let’s look at the results of rollup, which has a similar configuration to WebPack

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

Rollup resolves module dependencies by introducing them sequentially to the same file. The rollup solution would have been problematic to unpack because the modules are completely transparent, but it’s the perfect solution for library developers

'use strict';

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

bar();
Copy the code

Modular scheme

Prior to ES6 modularization, the JS community explored several modular systems, such as CommonJS in Node, AMD in browsers, and UMD that could be compatible with different modular systems at the same time. If you are interested in this section, you can check out my previous article “The History of JavaScript Modules”.

For browser-native, precompiled tools, and Node, modularity schemes vary from environment to environment; Since the browser environment cannot parse third-party dependencies, the browser environment needs to package dependencies as well. The files referenced in different environments are different. Here is a table for comparison

Browser (Script,AMD,CMD) Precompilation tools (Webpack,rollup, FIS) Node
Reference file index.aio.js index.esm.js index.js
Modular scheme UMD ES Module commonjs
Their own rely on packaging packaging packaging
Third party dependencies packaging Don’t pack Don’t pack

Note: Module systems in Legacy mode are compatible with IE6-8, but due to a rollupbugAdded: In Legacy mode, export and export default cannot be used at the same time

tree shaking

Rollup is a natural support for Tree shaking. Tree shaking can bring up parts of dependencies that are not used, which is very helpful for third-party dependencies and can greatly reduce package sizes

For example, if index.js just uses a function isString from the is.js package, no Treeshaking will reference is.js in its entirety

With Treeshaking, you can strip out all other functions in is.js, leaving only the isString function

specification

There are no rules, especially for open source projects, because there are many people involved, so adhering to one specification will get more results with less effort

Editor specification

Editorconfig can be used to ensure consistent indentation and line breaks, which is supported by most browsers. See here

The following configuration sets the use of Spaces instead of TAB in JS, CSS, and HTML. TAB is 4 Spaces, uses the Unix newline character, uses the UTF8 character set, and adds a blank line to the end of each file

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

Copy the code

Code style.

Eslint can be installed and configured in jslib-base to verify code. The esLint configuration file is located in config/.eslintrc.js

$ npm run lint
Copy the code

The design specification

Eslint can only guarantee code specification, but not good interface design. There are guidelines for functional interface design

The number of arguments

  • A function cannot have more than five arguments

Optional parameters

  • Optional parameters should come later
  • When the number of optional arguments exceeds three, objects can be passed in
  • Optional, should provide a default value

Parameter verification and type conversion

  • Mandatory parameters. If not, an error will be reported
  • The following types are mandatory, and an error is reported if the type is incorrect (object, array, function).
  • The following types will be converted automatically (number, string, Boolean)
  • For internal data of compound types, do the same two steps above
  • If number is NaN, special treatment should be done. (If there is a default value, an error should be reported.)

The parameter types

  • Use value types whenever possible for parameters (simple types)
  • Avoid complex types for parameters (to avoid side effects)
  • When using complex types, don’t go too deep
  • When using complex data types, you should make deep copies (to avoid side effects)

Function return value

  • Return value Can return operation result (get interface), operation success (save interface)

  • The type of the return value must be consistent

  • Use value types whenever possible (simple types) for return values

  • Do not use complex types for return values (to avoid side effects)

Version of the specification

The release should conform to the semantic version common to the open source community

Version number format: X.Y.Z

  • X Major version number, incompatible changes
  • Y Version number, compatible with changes
  • Z Revised version number, bug fixes

Git commit specification

Code should be submitted in accordance with the specification, and I recommend one of my specifications

test

A library without unit testing is a fraud. Unit testing can ensure that every delivery is of quality. Business code can avoid unit testing due to one-time and time costs, but open source libraries require repeated iterations and high quality requirements, so unit testing is essential

There are many technical solutions for unit testing. One option is Mocha + CHAI, which is a unit testing framework for organizing, running, and producing test reports. Chai is an assertion library used to do assertion functionality for unit tests

Since CHAI is not compatible with IE6-8, I chose another assertion library, Expect. Js. Expect is a BDD assertion library, which is very compatible, so I chose Mocha +expect

The difference between BDD and TDD is not repeated here, and interested students can refer to relevant materials by themselves

With a framework for testing, you also need to write code for unit tests. Here is an example

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

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

describe('Unit Tests'.function() {
    describe(Function of '1'.function() {
        it('equal'.function() {
            expect(1).to.equal(1);
        });
    });
});

Copy the code

Then simply run the following command and Mocha will automatically run the JS files under the test directory

$ mocha
Copy the code

Mocha supports testing in Nodes and browsers, but the framework has a problem in browsers. Browsers cannot support require(‘expect. Js ‘)

<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>
Copy the code

Here is an example of generating a test report with Mocha, on the left in Node and on the right in a browser

Sustainable integration

Libraries without sustainable integration are primitive, and it would be nice to run unit tests automatically every push without having to run them manually. Fortunately, Travis – CI already provides this functionality

Log in to Travis – Ci on GitHub and see your project on GitHub. Then you need to turn on the project to enable automatic integration

The second step is to add a file. Travis. Yml to the project, which will automatically run NPM test on Node 4, 6 and 8 for each push

language: node_js
node_js:
  - "8"
  - "6"
  - "4"
Copy the code

Other content

The open source library hopes to get users’ feedback. If there are requirements on the issues proposed by users, a template can be set to standardize the issues reported by users on Github, which need to formulate some information

Github /ISSUE_TEMPLATE can provide a template for issue by providing. Github /ISSUE_TEMPLATE file. Here is an example

What is the ### problemGive as detailed a description of the problem as possible# # # environment- Phone: Mi 6 - System: Android 7.1.1 - Browser: Chrome 61 - Jslib-base version: 0.2.0 - Other version information### online examplesPlease provide online examples if available# # # otherOther informationCopy the code

jsmini

Jsmini is a series of libraries based on Jslib-Base, jSMini concept is small and beautiful, and no third party dependence, open source many capabilities, can help library developers

conclusion

Five years in a flash, this article summarizes my experience of doing open source projects, hoping to help you, all the content can be found in jslib-base

Jslib-base is a ready-to-use scaffolding that enables third-party JS libraries to open source and quickly open source a standard JS library

Finally, I would like to give you a word: to open source a project, it is important to start and stick to it

Finally, I’d like to recommend my new book React State Management and Isomorphism For an in-depth look at cutting-edge isomorphism technology. Thank you for your support

Jingdong: item.jd.com/12403508.ht…

Down-down: product.dangdang.com/25308679.ht…

Finally recruitment front end, back end, client end! Location: Beijing + Shanghai + Chengdu, interested students, you can send your resume to my email: [email protected]

The original url: yanhaijing.com/javascript/…