[英] 2018 如何创建 JavaScript 库 (Part 1)

时间:2021-1-8 作者:admin

x * x * x

// square.js
export default x => x * x

So far so good. What now?

The most popular opinion is that **it’s best to publish the code with ES5 syntax**, so we’ll need to transpile our library. We’ll use [**Babel**](/go/?target=https%3A%2F%2Fbabeljs.io) for this — there are other solutions available (e.g. [Bublé](/go/?target=https%3A%2F%2Fbuble.surge.sh%2Fguide%2F)), but Babel has the most features and the whole plugin ecosystem available.

We could use `babel-cli` (`babel src --out-dir dist`), but generally speaking, Babel works with single files, not with whole projects. Of course, we could use it to transpile a catalog of files, but this would result in creating a copy of the catalog tree.

If a library consists of more than one file, a good practice is to use a **bundler** to create a so-called flat bundle (all project files merged into one). The obvious choice here is [**Rollup**](/go/?target=https%3A%2F%2Frollupjs.org%2Fguide%2Fen). Why? Because whenever possible, this tool doesn’t add any extra code, runtime wrappers, etc. Thanks to this approach:

*   the final files are **lightweight** (so that less code is shipped with an application which uses this library),
*   each file **creates just one scope**, so that static analysis is much easier (which matters for tools such as [UglifyJS](/go/?target=https%3A%2F%2Fgithub.com%2Fmishoo%2FUglifyJS2)).

Rollup and Babel have different objectives, but they can be freely used together one project.

### Installation

To install Rollup and Babel, run the following command:

npm install –save-dev rollup babel-core babel-preset-env rollup-plugin-babel

Note that this installs also `babel-preset-env`. Why would we need it? It **enables the necessary Babel plugins** on the basis of the environment definition that we may pass to it (by default it transpiles to ES5). Moreover, if we configure it properly, we can **use only selected transforms**, which is useful when, for example, our project doesn’t have to work in older browsers and we don’t have to transpile everything.

Other useful plugins are `babel-plugin-transform-object-rest-spread` and `babel-plugin-transform-class-properties`, so let’s install them right away:

npm install –save-dev babel-plugin-transform-object-rest-spread babel-plugin-transform-class-properties

### Configuration

Now when we have everything installed, we can start the **configuration process**. For full control, we will use a **pure JavaScript configuration file**. Babel 6 doesn’t support .babelrc.js files out of the box (upcoming Babel 7 does, though), but here’s a workaround – just add these two files:

#### .babelrc

{ “presets”: [“./.babelrc.js”] }

#### .babelrc.js

const { BABEL_ENV, NODE_ENV } = process.env

cosnt cjs = BABEL_ENV === ‘cjs’ || NODE_ENV === ‘test’

module.exports = {
presets: [
[‘preset-env’, { loose: true, modules: false }]
‘transform-object-rest-spread’,
‘transform-class-properties’,
],
plugins: [
cjs && ‘transform-es2015-modules-commonjs’
].filter(Boolean)
}

Comments:

*   For safety reasons, it’s best to **disable module transpiling** (with _modules: false_ passed to the preset-env) until a script requires it (opt-in with NODE_ENV or BABEL_ENV environmental variables).
*   Some tools support both **ESM** and **CJS** module formats, but work better with ESM. It’s easy to go overboard with transpiling here, but, on the other hand, if we don’t transpile all necessary modules, we will notice it right away. If we, for example, forget to activate CJS transform, the tool that requires CJS modules will crash with a bang.
*   Commonjs transform has `loose` option (read more [here](/go/?target=https%3A%2F%2Fgithub.com%2Fbabel%2Fbabel%2Ftree%2Fcb8c4172ef740aa562f0873d602d800c55e80c6d%2Fpackages%2Fbabel-plugin-transform-es2015-modules-commonjs%23loose)), but it can **cause more problems than it solves**, especially when we use **namespace import** (import * as mod from ‘./mod’).

The best approach is publishing **different versions of a module for various use cases**. A good example is [package.json](/go/?target=https%3A%2F%2Fdocs.npmjs.com%2Ffiles%2Fpackage.json) which contains two “entry point” versions: main and module. [Main](/go/?target=https%3A%2F%2Fdocs.npmjs.com%2Ffiles%2Fpackage.json%23main) is the standard field used by node.js ecosystem and therefore it should point to a CJS file. [Module](/go/?target=https%3A%2F%2Fgithub.com%2Frollup%2Frollup%2Fwiki%2Fpkg.module) field, on the other hand, points to an ES module file (transpiled exactly like the main “entry point” minus the modules). This field is mainly used by web app bundlers like [webpack](/go/?target=https%3A%2F%2Fwebpack.js.org%2F) or Rollup.

Thanks to the **static nature of ES module files**, it’s easier for bundlers to analyze their structure and optimize the files using techniques such as tree-shaking (removing unused imports) and scope-hoisting (putting most of the code within a single JavaScript scope).

To configure these two fields, let’s add the following lines to our package.json:

“main”: “dist/foo.js”,
“module”: “dist/foo.es.js”,

**Note:** In some cases for better compatibility it’s best to **omit file extensions** and let each tool add them by itself. This will be discussed in next part of the article.

Now we will need a script to build everything for us:

“build”: “rollup -c”

And a Rollup configuration file (rollup.config.js):

import babel from ‘rollup-plugin-babel’
import pkg from ‘./package.json’

const externals = [
…Object.keys(pkg.dependencies || {}),
…Object.keys(pkg.peerDependencies || {}),
]

const makeExternalPredicate = externalsArr => {
if (externalsArr.length === 0) {
return () => false
}
const externalPattern = new RegExp(^(${externalsArr.join('|')})($|/))
return id => externalPattern.test(id)
}

export default {
input: ‘src/index.js’,
external: makeExternalPredicate(externals),
plugins: [
babel({ plugins: [‘external-helpers’]})
],
output: [
{ file: pkg.main, format: ‘cjs’ },
{ file: pkg.module, format: ‘es’ },
]
}

Comments:

*   Rollup is a bundler which merges all files and dependencies into one file, but we want to use it for a different purpose. We want it to build our library and leave our dependencies where they belong — in the dependencies. We do not want Rollup to inline dependencies’ source code into our output bundles — we want it to leave them as they are, referenced to with import statements.
*   _makeExternalPredicate _keeps subdirectories and files belonging to our dependencies as external too (i.e. _downshift/preact_ or _lodash/pick_)
*   As mentioned above, Babel works with single files. In order do deduplicate some common runtime logic, it may insert **a helper function** into your files.

Here’s a sample helper function:

// input
const a = {…b}
const c = {…a}

// output
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。