preface

Jslib-base is a basic framework that allows developers to easily develop their own JavaScript libraries.

Inspired by Yan Haijing’s 8102 how to write a modern JavaScript library, the project is linked here.

Demand profile

Recently in the project needs to be within a long history of js library to upgrade, this library USES iife + ES5 way to write, maintain and develop existing inconvenience in the future, and then got the building a write js library based framework of idea, just saw yan big article, start, finally reach the effect as follows:

  • Write source support for ES6+ and TypeScript
  • Packaged code supports multiple environments (support browser native, AMD, CMD, Webpack, Rollup, FIS, etc., support Node)
  • Convergence of project-related configuration, clear directory, easy to use
  • Automatically removing third-party dependencies from useless code
  • Initialize the frame with one click
  • Automatically generate API documentation
  • Integrate code style verification
  • Integrate COMMIT information verification and incremental code style verification
  • Integrate unit tests and test coverage
  • Integrate sustainable build tools and test results reporting

Directions for use

First clone repository to local and install dependencies:

$ git clone https://github.com/logan70/jslib-base.git
$ cd jslib-base
$ npm install
Copy the code

Initialize the framework and fill in the project name, variable name, and project address as prompted

$ npm run init
Copy the code

You can then happily develop in the SRC/folder (listen for change builds, see the results in real time) and pack up when you’re done

# listen to build
$ npm run dev
# Package build
$ npm run build
Copy the code

Finally, the packaged release:

# Change CHANGLOG and version information automatically
$ npm run release
# login NPM
$ npm login
# Release the NPM package
$ npm publish
Copy the code

Once published, you can use your own JavaScript libraries in a variety of environments:

// First NPM installs your js library
$ npm install yourLibName --save-dev

// use in the browser

yourLibName.xxx(xxx)

// use in es6 module specification
import yourLibName from 'yourLibName'
yourLibName.xxx(xxx)

// used in Node
const yourLibName = require('yourLibName')
yourLibName.xxx(xxx)
Copy the code

Isn’t it easy? For more information, read the technical implementation below, or go to the Github project (mainly welcome Star, haha).

Jslib – base portal

The technical implementation

The first thing to be clear is that to support ES6+ and TypeScript, we need to plan from the beginning. Ideally, we only need to make one change to switch users, so create a configuration file jslib.config.js for the project:

// jslib.config.js
module.exports = {
    srcType: 'js' // Source type, js or ts
}
Copy the code

Build packaging tool

Tools: Rollup + Babel + TypeScript

Related documents:

  • Rollup: Next generation packaging tool – Rollup
  • TypeScript: a superset of JavaScript – TypeScript Chinese
  • Babel: JavaScript compiler

I chose Rollup for the packaging tool because of its Tree Shaking capabilities and build volume advantages.

  • Only ES6 modules are supported. When building code, in code that uses ES6 modularity, your code is statically analyzed and only the code used is packaged.
  • Build volume: Webpack builds not only business logic code, but also includes code execution guide and module relationship record code, Rollup builds only business logic code, build volume dominates, summary is development library or framework use Rollup, application development choose Webpack.

Let’s look at the use of Rollup:

First install Rollup and related plug-ins

$ npm install rollup -D
Copy the code

Then create a build configuration file/rollupConfig/rollup config. Aio. Js:

// build/rollupConfig/rollup.config.aio.js
const  { srcType } = require('.. /.. /jslib.config')
export default {
  input: `src/index.${srcType}`./ / entry documents, to distinguish the js | ts
  output: {
    file: 'dist/index.aio.js'.// Build file
    format: 'umd'.// Output format, umD format support browser direct import, AMD, CMD, Node
    name: 'myLib'.// umD module name, used as a global variable name in the browser environment
    banner: '/* https://github.com/logan70/jslib-base */' // Insert the header content of the packaged file}}Copy the code

SRC /index.js to write the source code:

// src/index.js
export function foo() {
    console.log('Hello world! ')}Copy the code

Then run the command to package the build:

$ npx rollup --config build/rollupConfig/rollup.config.aio.js
Copy the code

Take a look at the packaged file dist/index.aio.js:

/* https://github.com/logan70/jslib-base */
(function (global, factory) {
  typeof exports === 'object' && typeof module! = ='undefined' ? factory(exports) :
  typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = global || self, factory(global.myLib = {})); } (this.function (exports) { 'use strict';

  function foo() {
    console.log('Hello world! ');
  }

  exports.foo = foo;

  Object.defineProperty(exports, '__esModule', { value: true });

}));
Copy the code

It’s perfect no, let’s go ahead and write:

// src/index.js
// ...
export const add = (num1, num2) = > num1 + num2
Copy the code

View after packing:

// dist/index.aio.js
// ...
function foo() {
  console.log('Hello world! ');
}

const add = (num1, num2) = > num1 + num2;

exports.foo = foo;
exports.add = add;
// ...
Copy the code

????? Const and arrow functions?

It turned out that I forgot to compile. When IT comes to compiling, I think of the second half of this year…

Babel Rollup: Babel Rollup: Babel Rollup: Babel Rollup: Babel Rollup

Click to learn more about Babel

$ npm install @babel/core @babel/preset-env @babel/plugin-transform-runtime   -D
$ npm install @babel/polyfill @babel/runtime -S
$ npm install rollup-plugin-babel rollup-plugin-node-resolve rollup-plugin-commonjs -D
Copy the code
The name of the role
@babel/core Babel core
@babel/preset-env JS new syntax conversion
@babel/polyfill Add compatibility methods for all apis
@babel/plugin-transform-runtime & @babel/runtime Simplify the code by changing the help-class methods from defined before each use to uniform require
rollup-plugin-babel Rollup’s Babel plugin
rollup-plugin-node-resolve Rollup resolves external dependency module plug-ins
rollup-plugin-commonjs Rollup only supports ES6 modules, which are plug-ins that convert externally dependent CommonJS modules to ES6 modules

Then modify the Rollup configuration:

// build/rollupConfig/rollup.config.aio.js
const babel = require('rollup-plugin-babel')
const nodeResolve = require('rollup-plugin-node-resolve')
const commonjs = require('rollup-plugin-commonjs')
const { srcType } =  require('.. /.. /jslib.config')

export default {
  input: `src/index.${srcType}`.// Import file
  output: {
    // ...
  },
  plugins: [
    // Rollup resolves external dependency module plug-ins
    nodeResolve(),
    Rollup only supports ES6 modules, which are plug-ins that convert external dependencies from CommonJS modules to ES6 modules
    commonjs({
      include: 'node_modules/**',
    }),
    babel({
      presets: [['@babel/preset-env',
          {
            targets: {
              browsers: 'last 2 versions, > 1%, ie >= 6, Android >= 4, iOS >= 6, and_uc > 9'.node: '0.10'
            },
            // Whether to convert ES6 modules to CommonJS modules, the value must be false
            // Otherwise Babel will convert our module to CommonJS before Rollup has a chance to do any processing, causing some processing of Rollup to fail
            // For example, rollup-plugin-commonJS converts commonJS into ES6 modules
            modules: false.// Loose mode, can be enabled when the source code is not used at the same time export and export default, better compatible with IE8 below
            loose: false.// Polyfill as needed
            useBuiltIns: 'usage'}]],plugins: ['@babel/plugin-transform-runtime'].runtimeHelpers: true.exclude: 'node_modules/**'}})]Copy the code

Pack again and view:

// dist/index.aio.js
// ...
function foo() {
  console.log('Hello world! ');
}
var add = function add(num1, num2) {
  return num1 + num2;
};

exports.foo = foo;
exports.add = add;
// ...
Copy the code

Call it a day! The next step is to address TypeScript support by installing dependencies:

$ npm install typescript rollup-plugin-typescript2 -D
Copy the code
The name of the role
typescript The typescript core
rollup-plugin-typescript2 Rollup compiles typeScript plug-ins

Then create the TypeScript compiler configuration file tsconfig.json:

// tsconfig.json
{
    "compilerOptions": {
        "target": "ES5"."module": "ES6"."lib": ["esnext"."dom"]."esModuleInterop": true
    },
    "include": [
        "src/**/*.ts"]."exclude": [
        "node_modules"."**.d.ts"]}Copy the code

Because the Rollup will compile the plug-in dynamically switch depending on the type of source, so we create a file named/rollupConfig/getCompiler js to dynamic export Rollup compile the plug-in:

// build/rollupConfig/getCompiler.js 
const babel = require('rollup-plugin-babel')
const typeScript = require('rollup-plugin-typescript2')
const { srcType } = require('.. /.. /jslib.config')

const jsCompiler = babel({
  // ...
})

const tsCompiler = typeScript({
  // Override tsconfig.json configuration. Rollup supports only ES6 modules
  tsconfigOverride: {
    compilerOptions : { module: 'ES6'.target: 'ES5'}}})module.exports = (a)= > srcType === 'js' ? jsCompiler : tsCompiler
Copy the code

Then modify the Rollup configuration file:

const nodeResolve = require('rollup-plugin-node-resolve')
const commonjs = require('rollup-plugin-commonjs')
const getCompiler = require('./getCompiler')
const { srcType } =  require('./jslib.config')

export default {
  input: `src/index.${srcType}`.// Import file
  output: {
    // ...
  },
  plugins: [
    nodeResolve(),
    commonjs({
      include: 'node_modules/**',
    }),
    getCompiler()
  ]
}
Copy the code

SRC /index.ts SRC /index.ts

export function foo() :void {
  console.log('Hello world! ')}export const add: (num1: number, num2: number) = > number
  = (num1: number, num2: number) :number= > num1 + num2
Copy the code

Remember to change the source type in jslib.config.js to ts:

module.exports = {
  srcType: 'ts' / / source type, js | ts
}
Copy the code

Dist /index.aio.js file (dist/index.aio.js)

Multi-environment support

Tools:

  • Semver: check Node version tool – semver
  • Minimist: Parsing command-line argument tool – Minimist

NPX rollup –config 1.js && NPX rollup –config 2.js Therefore, Node APIS provided by Rollup and promise. all are used to make full use of the asynchronous features of JS and improve the construction efficiency.

To do a good job, he must sharpen his tools. Considering that other commands may be executed using Node later on, we’ll implement a CLI of our own.

Since we will be writing with new JS features, we require version larger than 8, so we use semver:

$ npm install semver -D
Copy the code

Then create a new build/index.js file as our Node entry:

// build/index.js
const semver = require('semver')
const requiredVersion = '> = 8'
// check node version
if(! semver.satisfies(process.version, requiredVersion)) {console.error(
    `You are using Node ${process.version}, but @logan/jslib-base ` +
    `requires Node ${requiredVersion}.\nPlease upgrade your Node version.`
  )
  process.exit(1)}Copy the code

Then we switched to Node version 7 to test:

Minimist = minimist; minimist = minimist; minimist = minimist;

$ npm install minimist -D
Copy the code

Minimist is simple to use:

const args = require('minimist')(process.argv.slice(2))
console.log(args)
Copy the code

Let’s look at the results:

I think you get the idea, so let’s go ahead and write the Node entry:

// build/index.js
// ...
// Parse command line arguments
const args = require('minimist')(process.argv.slice(2))
// fetch the first one as the command
const command = args._[0]
// Remove the command from args._
args._.shift()

function run(command, args) {
  // Dynamically load the command execution file
  const runner = require(`./command-${command}/index`)
  // Pass args as an argument and execute the corresponding task function
  runner(args)
}

run(command, args)
Copy the code

If we want to add tasks later, we simply create the command-${task name} folder, write code in the index.js folder, and add the corresponding script command in package.json.

Let’s start by adding build commands to package.json:

// package.json
{
  "scripts": {..."build": "node build/index.js build". }}Copy the code

Then create build/command-build/index.js and write the code to perform the build task:

Rollup configuration files in other output formats and the use of Node apis provided by Rollup are not explained in detail

const path = require('path')
const rollup = require('rollup')

// Different environment configuration file mapping
const rollupConfigMap = {
  / / UMD
  aio: 'rollup.config.aio.js'.// UMD compressed version
  aioMin: 'rollup.config.aio.min.js'.// ES6 module format
  esm: 'rollup.config.esm.js'./ / CommonJS format
  cjs: 'rollup.config.js'
}

// Single rollup build task
function runRollup(configFile) {
  return new Promise(async (resolve) => {
    // Import rollup configuration based on configuration file name
    const options = require(path.resolve(__dirname, '.. /rollupConfig', configFile))
    // Create a rollup task
    const bundle = await rollup.rollup(options.inputOption)
    // Build file
    await bundle.write(options.outputOption)
    console.log(`${options.outputOption.file}Build success ')
    resolve()
  })
}

module.exports = async (args = {}) => {
  // The format array to build
  const moduleTypes = args._

  // The purpose is to support the selection of types to build
  // For example, node build/index.js build esm CJS only builds es6 modules and commonJS files
  // If not, build all
  const configFiles = moduleTypes && moduleTypes.length
    ? moduleTypes.map(moduleKey= > rollupConfigMap[moduleKey])
    : Object.values(rollupConfigMap)

  try {
    // Parallel build (pseudo, JS single thread)
    await Promise.all(configFiles.map(file= > runRollup(file)))
  } catch (e) {
    throw new Error(e)
  }
}
Copy the code

Then we run the build command NPM run build to see the effect:

Code style checking

Tools:

  • ESLint: JavaScript code style verification tool – ESLint Chinese documentation
  • TSLint: TypeScript code style validation tool – TSLint official website

First install dependencies:

$ npm install eslint eslint-config-airbnb eslint-plugin-import -D
Copy the code

Configure the ESLint verification rule file. Eslintrc.js. The detailed procedure is omitted. For details, go to the official website.

The JavaScript code uses the Airbnb JavaScript style as a base, with no semicolon rules (individual/team preferences can be modified) to validate the code style.

Configure the TSLint verification rule file tslint.json. Details are omitted. For details, go to the official website above.

TypeScript code uses default rules, along with single-quote, no-semicolon rules that can be modified depending on individual/team preferences, to validate code style.

Then add the validation command to package.json:

// package.json
{
  "scripts": {..."lint": "node build/index.js lint"."lint:fix": "node build/index.js lint --fix". }}Copy the code

Then create build/command-jslint/index.js and write the code to perform the build task:

// build/command-jslint/index.js
// Node process method
const { spawn } = require('child_process')
const { srcType } = require('.. /.. /jslib.config')

module.exports = async (args = {}) => {
  const options = [
    // The file to be verified, glob matches
    `src/**/*.${srcType}`.// Error output format, I prefer codeFrame style, more detailed information
    '--format'.'codeframe'
  ]
  // Whether to automatically fix, NPM run lint:fix enabled
  if (args.fix) {
    options.push('--fix')}// The lint tool to use
  const linter = srcType === 'js' ? 'eslint' : 'tslint'
  // Start the child process
  spawn(
    linter,
    options,
    // The information is output to the main process
    { stdio: 'inherit'})}Copy the code

Then let’s test it out:

JavaScript Code Style Check and Fix:

TypeScript code style Checking and fixing:

Automatically generate API documentation

The use of tools

  • JSDoc: Automatic generation of API documentation from JS annotations
  • Docdash: JSDoc theme, support search and other features – DocDash
  • TypeDoc: Automatic generation of API documentation from TS comments – TypeDoc official website
  • Typedoc-plugin-external-module-name: optimizes TypeScript document module classification plug-ins – typedoc-plugin-external-module-name

First install dependencies:

$ npm install jsdoc typedoc typedoc-plugin-external-module-name -D
Copy the code

Configure the JSDoc file build/command-doc/ jsDocconf. js. For details, go to the official website.

Configure the TypeDoc file build/command-doc/ tsDocconf. js. For details, go to the official website.

Then add the command to generate the API document in package.json:

// package.json
{
  "scripts": {..."doc": "node build/index.js doc". }}Copy the code

Create build/command-doc/index.js and write code that performs the task of generating API documentation:

// build/command-jslint/index.js
// Node process method
const { spawn } = require('child_process')
const path = require('path')
const TypeDoc = require('typedoc')
const { srcType } = require('.. /.. /jslib.config')

module.exports = async (args = {}) => {
  if (srcType === 'js') {
    spawn('jsdoc'['-c', path.resolve(__dirname, './jsdocConf.js')] and {stdio: 'inherit' })
    resolve()
  } else {
    // Introduce the tsdoc configuration
    const tsdocConf = require(path.resolve(__dirname, './tsdocConf'))
    // Initialization task, see typeDoc official website
    const app = new TypeDoc.Application(tsdocConf)
    const project = app.convert(app.expandInputFiles(['src']))
    if (project) {
      const outputDir = tsdocConf.outputDir
      // Output the document
      app.generateDocs(project, outputDir)
    }
  }
}
Copy the code

We then added JavaScript normalized comments to the source code, and the related comment standards can also be viewed at the JSDoc website:

// src/index.js
/ * * * @ module umdName * @ description JavaScript libraries - umdName * * @ example * / / @ see https://github.com/logan70/jslib-base * // Import files: 
/** * @description add function * @method add * @memberof module:umdName * @param {Number} num1 - add * @param {Number} num2 - Addend * @return {Number} - Add two numbers * @example * umdname. add('Hello World! ') * /
export const add = (num1, num2) = > num1 + num2
Copy the code

Then change the source type in jslib.config.js to js, run the command NPM run doc, open docs/index.html to see the result:

We then add TypeScript canonical annotations to the source code, which can also be found on TypeDoc’s website:

Note: TypeDoc does not support the @example tag, but does support the MarkDown syntax, so we can write code instances inside the MD tag

/ * * * @ module umdName * @ description JavaScript libraries - umdName * * @ example * @ see https://github.com/logan70/jslib-base Js * * // use in browser * // import file: 

/** * @description add function * @param num1 - addend * @param num2 - addend * @returns result of two numbers * @example * ' 'js ** umdName. Add (1, 2) * ` ` ` * /
export const add: (num1: number, num2: number) = > number
  = (num1: number, num2: number): number= > num1 + num2
Copy the code

Then change the source type in jslib.config.js to ts, run the command NPM run doc, open docs/index.html to see the result:

Unit testing and test coverage

The use of tools

  • Jest: Unit Testing Framework – Jest Chinese documentation
  • Babel-jest: the new syntax feature of JS is supported by the plug-in babel-jest
  • Ts-jest: TypeScript supports plugins – TS-jest
  • @type/jest: TypeScript jest declaration plugin – @type/jest

First install dependencies:

$ npm install jest babel-jest ts-jest @type/jest -D
Copy the code

Compile the jest file build/command-test/jest.config.js.

// build/command-test/jest.config.js
const path = require('path')

module.exports = {
  // Root path, pointing to the project root path
  rootDir: path.resolve(__dirname, '.. /.. / '),
  // Array of paths found by jest, add project root path
  "roots": [
    path.resolve(__dirname, '.. /.. / ')].// ts-jest supports typescript, babel-jest supports ES6 modularized syntax
  "transform": {
    "^.+\\.tsx? $": "ts-jest"."^.+\\.jsx? $": "babel-jest"
  },
  // Test the file to match the re
  "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx? $".// Test the file suffixes that can be omitted from the file
  "moduleFileExtensions": ["ts"."js"].// Display the test contents
  "verbose": true
}
Copy the code

NPM run test:coverage is the command to collect test coverage information for unit tests, which will be covered later:

// package.json
{
  "scripts": {..."test": "node build/index.js test"."test:coverage": "node build/index.js test --coverage". }}Copy the code

Since the new syntax features of JS require Babel compilation, we create and write a Babel configuration file.

// .babelrc
{
  "presets": ["@babel/preset-env"]}Copy the code

Then create build/command-test/index.js and write the code to perform the unit test task:

// build/command-test/index.js
const { spawnSync } = require('child_process')

module.exports = (args = {}) = > {
  return new Promise(resolve= > {
    // Specify the jest configuration file
    const cliOptions = ['--config'.'build/command-test/jest.config.js']
    // Whether to collect test coverage information
    if (args.coverage) {
      cliOptions.push('--collectCoverage')
    }
    spawnSync('jest', cliOptions, {
      stdio: 'inherit'
    })
    resolve()
  })
}
Copy the code

Then we create a new __tests__ folder under the project root directory to write unit test cases. Please visit Jest Chinese documentation for more unit test knowledge:

// __tests__/index.test.js
import { add } from '.. /src/index.js'

describe('Unit Tests (JS)', () => {
  it('One plus two is three'., () => {
    expect(add(1.2)).toEqual(3)})})// __tests__/index.test.ts
import { add } from '.. /src/index'

describe('Unit Tests (TS)', () => {
  it('One plus two is three'., () => {
    expect(add(1.2)).toEqual(3)})})Copy the code

Then run the NPM run test command to see the result:

We then run the command NPM run test:coverage to see the test coverage information:

Test coverage information can also be viewed by opening coverage/lcov-report/index.html in a browser:

Help information

The use of tools

  • Chalk: Command line shader – Chalk
  • Ascii-art: character conversion tool – ASCIi-art

First install dependencies:

$ npm install chalk ascii-art -D
Copy the code

Then add the command to display the help information in package.json:

// package.json
{
  "scripts": {..."help": "node build/index.js help". }}Copy the code

Then create build/command-help/index.js and write the code to execute the output help task:

const art = require('ascii-art')
const chalk = require('chalk')

module.exports = (a)= > {
  return new Promise(resolve= > {
    // Generate character painting
    art.font('@logan\/jslib\-base'.'Doom', data => {
      console.log(chalk.cyan((The '-').repeat(104)))
      console.log(chalk.cyan(data))
      console.log(chalk.cyan((The '-').repeat(104)))
      console.log()
      console.log('Usage: npm run <command>')
      console.log()
      console.log('A good JavaScript library scaffold.')
      console.log()
      console.log('Commands:')
      console.log(' npm run init, initialize this scaffold.')
      console.log(' npm run build, output bundle files of three different types(UMD, ES6, CommonJs).')
      console.log(' npm run dev, select a type of output to watch and rebuild on change.')
      console.log(' npm run lint, lint your code with ESLint/TSLint.')
      console.log(' npm run lint:fix, lint your code and fix errors and warnings that can be auto-fixed.')
      console.log(' npm run doc, generate API documents based on good documentation comments in source code.')
      console.log(' npm run test, test your code with Jest.')
      console.log(' npm run test:coverage, test your code and collect coverage information with Jest.')
      console.log(' npm run help, output usage information.')
      console.log()
      console.log(`See more details at ${chalk.cyan('https://github.com/logan70/jslib-base')}`)
      resolve()
    })
  })
}
Copy the code

Then we run the command NPM run help to see the effect:

One-click rename

The idea of one-click rename is to obtain the user input information, and then replace the placeholder in the relevant file with the user input information.

The use of tools

  • Inquirer: Interactive command line tool – Inquirer

First install dependencies:

$ npm install inquirer -D
Copy the code

Then add the command to initialize scaffolding in package.json:

// package.json
{
  "scripts": {..."init": "node build/index.js init". }}Copy the code

Inquirer uses the method to go to the Inquirer documentation to learn, here does not repeat, directly on the code.

Then create build/command-init/index.js and write the code that performs the scaffolding initialization task:

// build/command-init/index.js
const fs = require('fs')
const path = require('path')
const inquirer = require('inquirer')
// Displays help information
const runHelp = require('.. /command-help/index')

// Queue of tasks to be executed by inquirer
const promptArr = []
// Get the UMD output name
promptArr.push({
  type: 'input'.name: 'umdName'.// Prompt message
  message: 'Enter the name for umd export (used as global varible name in browsers):'.// Verify user input
  validate(name) {
    if (/^[a-zA-Z][\w\.]*$/.test(name)) {
      return true
    } else {
      // Verification failure message
      return `Invalid varible name: ${name}! `}}})// Get the project name
promptArr.push({
  type: 'input'.name: 'libName'.message: 'Enter the name of your project (used as npm package name):',
  validate(name) {
    if (/^[a-zA-Z@][\w-]*\/? [\w-]*$/.test(name)) {
      return true
    } else {
      return `Invalid project name: ${name}! `}}})// Get the project address
promptArr.push({
  type: 'input'.name: 'repoUrl'.default: 'https://github.com/logan70/jslib-base'.message: 'Enter the url of your repository:',
  validate(url) {
    if (/^https? \:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?) ([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_]*)? $/.test(url)) {
      return true
    } else {
      return `Invalid repository url: ${url}! `}}})module.exports = (args = {}) = > {
  return new Promise(async (resolve, reject) => {
    // Get user input
    const { umdName, libName, repoUrl } = await inquirer.prompt(promptArr)

    // The file needs to be modified
    let files = [
      'jslib.config.js'.'package.json'.'package-lock.json'.'README.md'
    ]

    try {
      await Promise.all(files.map((file) = > new Promise((resolve, reject) = > {
        const filePath = path.resolve(__dirname, '.. /.. / ', file)
        // Read the file
        fs.readFile(filePath, 'utf8'.function (err, data) {
          if (err) {
            reject(err)
            return
          }
          // Replace the placeholder
          const result = data
            .replace(/umdName/g, umdName)
            .replace(/@logan\/jslib\-base/g, libName)
            .replace(/https:\/\/github\.com\/logan70\/jslib/g, repoUrl)
        
          // Rewrite the file
          fs.writeFile(filePath, result, 'utf8', (err) => {
             if (err) {
               reject(err)
               return
             }
             resolve()
          })
        })
      })))
      // Displays help information
      await runHelp()
    } catch (e) {
      throw new Error(e)
    }
  })
}
Copy the code

Then we run the command NPM run init to see the effect:

Watch listens for build patterns

The idea for listening builds is that the user selects an output format and uses the Node API provided by Rollup to enable Watch mode.

Start by adding the command to initialize scaffolding in package.json:

// package.json
{
  "scripts": {..."dev": "node build/index.js watch". }}Copy the code

Then create build/command-watch/index.js and write the code to perform the listening build task:

const path = require('path')
const rollup = require('rollup')
const inquirer = require('inquirer')

const { srcType } = require('.. /.. /jslib.config')

// rollup listens for configuration
const watchOption = {
  // Replace native file change listener with chokidar
  chokidar: true.include: 'src/**'.exclude: 'node_modules/**'
}

// The user selects an output format
const promptArr = [{
  type: 'list'.name: 'configFile'.message: 'Select an output type to watch and rebuild on change:'.default: 'rollup.config.aio.js'.choices: [{
    value: 'rollup.config.aio.js'.name: 'UMD - dist/index.aio.js (Used in browsers, AMD, CMD.)'
  }, {
    value: 'rollup.config.esm.js'.name: 'ES6 - dist/index.esm.js (Used in ES6 Modules)'
  }, {
    value: 'rollup.config.js'.name: 'CommonJS - dist/index.js (Used in Node)'}}]]module.exports = (args = {}) = > {
  return new Promise((resolve, reject) = > {
    // Get the output format selected by the user
    inquirer.prompt(promptArr).then(({ configFile }) = > {
      // Rollup configuration for the output format
      const customOptions = require(path.resolve(__dirname, '.. /rollConfig/', configFile))
      constoptions = { ... customOptions.inputOption,output: customOptions.outputOption,
        watch: watchOption
      }

      // Start listening
      const watcher = rollup.watch(options)

      // Time processing in the listening phase
      watcher.on('event'.async (event) => {
        if (event.code === 'START') {
          console.log('Building... ')}else if (event.code === 'END') {
          console.log('Build complete. ')}})})})}Copy the code

Then we run the command NPM run dev to see the effect:

Standardize Git commit information

The use of tools

  • Husky: Git hook tool – husky
  • @commitlint/config-conventional and @commitlint/cli: Git commit information verification tool – commitlint
  • Commitizen: A tool for writing qualified Commit messages. – commitizen
  • Lint-staged: Incremental validation code style tool – Lint-staged

Go to the above document to learn how to use it.

First install dependencies:

$ npm install husky @commitlint/config-conventional @commitlint/cli commitizen lint-staged -D
Copy the code

Then add the following information to package.json:

// package.json
{
  "scripts": {..."husky": {
        "hooks": {
          "pre-commit": "lint-staged"."commit-msg": "commitlint -E HUSKY_GIT_PARAMS"}},"lint-staged": {
        "src/**/*.js": [
          "eslint --fix"."git add"]."src/**/*.ts": [
          "tslint --fix"."git add"]},"commitlint": {
        "extends": [
          "@commitlint/config-conventional"]},... }}Copy the code

After the configuration is complete, let’s see the effect:

As expected, it is recommended to read Ruan Yifeng’s article about Git commit information specification

If you can’t remember or don’t want to, commitizen is a tool for writing qualified Commit messages.

Having installed the dependency, we run the following command to support Angular’s Commit Message format.

$ commitizen init cz-conventional-changelog --save --save-exact
Copy the code

Then add the Git commit command to package.json:

// package.json
{
  "scripts": {..."commit": "npx git-cz". }}Copy the code

After that, the project will replace the git commit command with NPM run commit command. Let’s look at the effect:

Standard-version, which automatically generates CHANGELOG and updates version information based on commit information, is also introduced

We’ll start by installing dependencies:

$ npm install standard-version
Copy the code

Then add the release command to package.json:

// package.json
{
  "scripts": {..."release": "standard-version". }}Copy the code

You can run the NPM run release command to automatically update changelog. md and version information based on Git Commit history.

Continuous integration

The use of tools

  • Travis-ci: Continuous integration tool – Travis-CI
  • Codecov: Test results analysis tool – Codecov

To start, add your Github project by logging in to Travis-CI and Codecov as Github accounts.

Then install dependencies:

$ npm install codecov -D
Copy the code

Then add the codecov command to package.json:

// package.json
{
  "scripts": {..."codecov": "codecov". }}Copy the code

Then create the Travis -ci profile under the project trace.travis. Yml:

language: node_js            # specify the runtime environment as Node

node_js:                     # specify nodejs version 8
  - "8"

cache:				               # Cache node_js dependencies to improve the efficiency of the second build
  directories:
  - node_modules


script:                      The script command to run
  - npm run test:coverage    Unit test and collect test coverage information
  - npm run codecov          Upload the unit test results to Codecov
Copy the code

We wrote the source code and unit tests, pushed them to a remote repository, and then went to see the results:

Travis – CI:

Codecov:

The README badge

At the end of the day, there’s nothing to say about README writing, which has its own style.

Mainly the README badge, after all the hard work, can’t do without an X.

Read the GitHub project badge for adding and setting

Tarvis-ci badge click on the badge to the right of the project name to obtain:

Codecov’s Badge is in the project Settings Badge:

Copy the badge content in Markdown format and paste it into readme. md.

conclusion

This period of hard work is not in vain, the process of learning a lot of things, see here handsome boys and girls, don’t be stingy, give a Star bai!

Github project portal

Github blog portal