closertb / closertb.github.io

浏览issue 或 我的网站,即可查看我的所有博客
https://closertb.site
32 stars 0 forks source link

从dist到es:发一个NPM库,我蜕了一层皮 #29

Open closertb opened 5 years ago

closertb commented 5 years ago

写于: 2019-02-22

这并不是自己第一次发npm包, 所以这里没有多少入门的知识。在此之前已经有一篇前端脚手架,听起来玄乎,实际呢?,但这一次的npm包和上一次的不是一个概念,前者只是一个脚本工具,而这个npm包是日常开发中方法和组件的集合, 是一个库。 在读本文前,假定你已经对npm包有一定概念,熟悉Babel编译和webpack打包的常规用法,知道一些前端工程化的知识。假如你也想自己发布一个npm仓库,但对这一块了解的不是很多,推荐webpack官方的创建一个 library

打包模式:日常构建与库的构建

在前端日常开发中,引入npm库,执行webpack构建已经是一件不能再平常的事情。但大多数时候,我们不关心这个npm库是怎样构成的,我们只需要知道怎么使用,像antd;在工程化成熟的公司,也不关心webpack的配置到底是怎样的,只需要npm run start或npm run build去启动一次热加载或打包。但是如果你是编写一个npm仓库,这些东西你都需要知道。从那时的无知说起,起初,我用公司的构建工具(类似于roadhog)去打包我的库,没有坎坷,构建出一个2M多的包并成功发布。

image

在测试项目中引入,构建成功。

import { EnhanceTable, WithSearch } from 'antd-doddle';  // 引入仓库

在浏览器中打开,打击开始到来: image 提示要引入的对象没有正确导出,就是没有做module.export,所以这是一个打包模式的问题,output.libraryTarget需要了解一下。

image webpack的output.libraryTarget决定了打包时对外暴露出来的对象是那种模式,默认是var,用于script标签引入,该模式也是我们日常开发构建最常用的模式,除了这一种,还支持的常用选项有:

image

本以为到这就结束了,但是这才开始。只编译不打包消除不必要的代码只是很小的原因,重要的东西我觉得换一行说比较好。 不顾语文老师的责骂换行,那什么才是是最重要的:按需打包(tree shaking),对于这种组件和方法库,作为使用者,我们希望他能支持按需打包,像lodash和antd这样。所以怀着好奇的心理我去看了他们的package.json,然后发现了这样的配置:

  "main": "lib/index.js",
  "module": "es/index.js",
  "name": "antd",

除了认知中的main入口定义,还多了一个module入口.为什么需要这样呢,和我一样无知的,可以先读webpack官方的tree shaking,如果不够直观,可以再看一位大佬写的一篇相关文章聊聊 package.json 文件中的 module 字段。看下面代码:

// es6 模块写法 fun.js
export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}

// commonJs写法 fun.js
exports.square = function(x) {
  return x * x;
}

exports.cube = function(x) {
  return x * x * x;
}

// index.js引入
import {
  square
} from './fun.js';

const res = square(10);
console.log('res:', res);

简单来说,就是index.js打包编译时,引入commonJs写法的fun.js,打包会将square与cube两个函数同时打进来。而引入es6写法的fun.js,只会将square打包。这样的操作,对于现在的主流趋势,就是必须的优化,特别对于lodash和antd这种庞大的库。而要使我们的库支持这样的操作,我们需要编译时,禁止babel将es6的module引入方式编译,其实只需要在前面的基础上多配置一个参数:

"@babel/preset-react" // lib的打包方式

["@babel/preset-env", { "modules": false }] // 保留es6模块引入的方式

得到的是下面这样的结果:

image

和上面的lib对比,感觉更接近原始代码。至此,编译已结束,但是我们还需要在package.json中加上相应的配置:

  "description": "antd后台项目前端组件封装和方法库",
  "main": "lib/index.js",
  "module": "es/index.js",
  "scripts": {
    "build": "webpack --config webpack.config.js",
    "lib": "gulp lib",
    "es": "gulp es",
    "prepublish": "gulp && webpack --config webpack.config.js"
  },
  "files": [
    "es",
    "dist",
    "lib",
    "utils"
  ],

怎么让库支持多目录输出

因为我的库主要包括组件和方法,我把方法放到一起,通过utils作为默认输出。然后项目中引入是这样的:

import { EnhanceTable, WithSearch }, utils from 'antd-doddle'; 

// 要用里面的方法需要再分解一次或通过utils.xxx
const { DATE_FORMAT, idCodeValid } = utils;

虽然感觉上不复杂,但是总感觉别扭,如果你用过dva,就见过下面这样的引入:

import { routerRedux } from 'dva/router';
import dva from 'dva';

所以我去学习了一下,发现要这样实现也不难 分三步,分目录打包,增加一个输出,并增加内部私有映射,package.json增加一个这个映射目录的输出。具体可查看项目源码。实现后,项目引入是这样的:

import { EnhanceTable, WithSearch }, utils from 'antd-doddle'; 
import { DATE_FORMAT, idCodeValid } from ‘antd-doddle/utils’; // 一步到位

小技巧分享

// 安装
npm install -g nrm
// 设置入口npm,cnpm,company
nrm add npm 'http://registry.npmjs.org'
nrm add cnpm 'https://registry.npm.taobao.org'
nrm add vnpm 'http://npm.company.com'
// 切换入口到淘宝入口
nrm use cnpm

后续

一个春节自己断断续续就在倒腾这个,收获还是挺大的。后面自己会慢慢去学习怎么加入demo‘,加入单元测试,去建造一个完整的npm库。 源码库:github npm仓库地址:npm