ts-lib-scripts
ts-lib-scripts,一个以
尽管 TypeScript 最近很火爆,但是要设置一个 TypeScript 库仍然很困难。ts-lib-scripts 能让你轻松创建、构建、测试和发布 TypeScript 库。别再浪费一个下午时间来处理TypeScript、Rollup、Babel、ESLint、tsconfig.json
、Yarn、Prettier和VSCode之间的和谐共处,用 ts-lib-scripts 来帮你节省出这些时间:
npx ts-lib-scripts create my-ts-lib
特性
ts-lib-scripts 关注构建 TypeScript 库需要的元素,并提供一些开箱即用的特性:
-
🎯 使用Rollup打包你的代码,支持CJS
、UMD
、ESM
输出格式和development
、production
模式。当然,还有.d.ts 文件。 -
📦 支持 tree-shaking,内置 lodash 优化、babel helpers、代码压缩,节省代码大小。 -
💄 友好的日志输出。 -
💯 内置 ESLint 和 Prettier 支持,让代码错误无所遁形。 -
🏎️ 针对 Git、GitHub 和 VSCode 做调优,完美的编程体验。 -
✨ 内含非常智能的Jest
配置,yarn test
即可享受美妙的单元测试。 -
🎣 Babel 宏:轻松扩展 Babel 配置。 -
🎉 无须配置,只需一个命令行。
快速开始
ts-lib-scripts 需要Node.js 10+和Yarn。
打开命令行,执行以下命令:
npx ts-lib-scripts create my-ts-lib
cd my-ts-lib
code .
yarn start
使用 VSCode 打开src/index.ts
,开始构建你的 TypeScript 库吧。
项目结构:
my-ts-lib
|__ .vscode
|__ src
|__ .editorconfig
|__ .gitignore
|__ package.json
|__ README.md
|__ tsconfig.json
创建的my-ts-lib
项目有很多有用的命令可用。如下介绍。
yarn build
打包,并将打包文件放在dist
文件夹中。使用 rollup 对代码做优化并打包成多种格式(Common JS
,UMD
和ES Module
)。
yarn lint
yarn lint
会检查整个项目是否有代码错误、风格错误。
开启 vscode 的 eslint、prettier 插件,在使用 vscode 编码时,就会自动修正风格错误、提示语法错误。
yarn format
yarn format
可以自动调整整个项目的代码风格问题。
yarn test
yarn test
以监听模式启动 jest,运行单元测试。
开启 vscode 的 jest 插件,会在文件变化时自动运行单元测试。
yarn release
发布 npm 包。
monorepo (v0.3.0)
从 0.3.0 开始,ts-lib-scripts
支持生成 monorepo 结构的项目:
npx ts-lib-scripts create my-ts-lib --monorepo
生成的项目结构如下:
my-ts-lib
|__ .vscode
|__ pacakges
|__ .editorconfig
|__ .gitignore
|__ lerna.json
|__ package.json
|__ README.md
|__ tsconfig.json
采用lerna管理模块的依赖和发布,所有模块都放在 pacakges 目录中。有以下几个命令行可用:
-
yarn build
- 编译所有模块 -
yarn lint
- 检查所有模块的代码 -
yarn test
- 以监听者模式执行所有模块的单元测试 -
yarn lerna publish
- 发布 -
yarn gen
- 添加新模块。例如:yarn gen module-a
。 -
yarn lerna add
- 添加依赖。
可以为所有模块添加依赖,如给所有模块添加ts-lib
依赖:
yarn lerna add ts-lib
也可以为单个模块添加依赖:
yarn lerna add immer --scope=module-a
优化
前端代码的大小是前端项目中至关重要的指标,但是压缩代码大小不能以牺牲开发者体验为代价。ts-lib-scripts 做代码优化就是本着这个原则来的。
开发日志
我们在开发时需要很多日志来记录和展示代码的运行情况,但是这些日志又不想打包到生产环境中去。使用 ts-lib-scripts,可以实现这种愿望。
我们的代码如下:
function sum(a: number, b: number) {
if (process.env.NODE_ENV !== 'production') {
console.log(`调用sum方法:${a} + ${b} = ${a + b}`);
}
return a + b;
}
打包之后,在dist
目录下生成下面三个文件:
index.js
:
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./ts-lib-scripts-example.cjs.production.js');
} else {
module.exports = require('./ts-lib-scripts-example.cjs.development.js');
}
my-ts-lib.cjs.development.js
:
'use strict';
function sum(a, b) {
{
console.log(
'调用sum方法:'
.concat(a, ' + ')
.concat(b, ' = ')
.concat(a + b),
);
}
return a + b;
}
exports.sum = sum;
my-ts-lib.cjs.production.js
:
'use strict';
exports.sum = function(t, r) {
return t + r;
};
如你所见,ts-lib-scripts 在生成生产模式(production mode)包时是会剔除掉process.env.NODE_ENV !== 'production'
而不会影响到开发模式(development mode)包。所以,你可以尽情地编写一些开发日志而不用担心加大生产包的大小。
warning
warning是非常有用的产生开发日志的库。用法如下:
// some script
var warning = require('warning');
var ShouldBeTrue = false;
warning(
ShouldBeTrue,
'This thing should be true but you set to false. No soup for you!',
);
// 'This thing should be true but you set to false. No soup for you!'
ts-lib-scripts 会将warning
代码转换成process.env.NODE_ENV !== 'production'
代码,如下所示:
warning(condition, argument, argument);
会被替换成:
if ('production' !== process.env.NODE_ENV) {
warning(condition, argument, argument);
}
使用 lodash
lodash是前端的瑞士军刀,在前端出现频次非常高,但是 lodash 库很大。ts-lib-scripts 内置了 lodash 打包优化。
要使用 lodash,你需要先安装 lodash:
yarn add lodash lodash-es @types/lodash
然后在你的代码中自由使用 lodash:
// ./src/index.ts
import { kebabCase } from 'lodash';
export const KebabLogger = (msg: string) => {
console.log(kebabCase(msg));
};
ts-lib-scripts 会将上面的代码翻译成:
import o from 'lodash-es/kebabCase';
const e = (e) => {
console.log(o(e));
};
export { e as KebabLogger };
也就是说,ts-lib-scripts 将import kebabCase from 'lodash/kebabCase'
转换成了import o from 'lodash-es/kebabCase'
。这样这些 lodash 代码可以被 treeshaking 优化。
babel helpers
ts-lib-scripts 使用 Babel 编译 TypeScript,在转换 ES6 语法时,需要一些胶水代码才能转换成 ES5 语法。如类:
index.js
:
export class Pointer {
construcor(private x: number, private y: number) {}
move(x: number, y: number) {
return new Point(this.x + x, this.y + y);
}
}
不经过优化编译后的代码如下:
'use strict';
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
/* eslint-disable import/prefer-default-export */
var Pointer =
/*#__PURE__*/
(function() {
function Pointer(x, y) {
_classCallCheck(this, Pointer);
this.x = x;
this.y = y;
}
_createClass(Pointer, [
{
key: 'move',
value: function move(x, y) {
return new Pointer(this.x + x, this.y + y);
},
},
]);
return Pointer;
})();
exports.Pointer = Pointer;
//# sourceMappingURL=my-ts-lib.cjs.development.js.map
编译后的代码中有_classCallCheck
、_createClass
、_defineProperties
三个函数,这三个函数就是为了支持 ES6 类语法的胶水代码,我们称之为 babel helpers。
如果我们的库代码中包含了这样的代码,那么被其他项目引用后,就很可能出现重复的这样的代码,这样就增加了应用的大小。
我们可以将胶水代码改成引用 babel helpers 相关的库。经过优化后,编译的代码如下:
'use strict';
function _interopDefault(ex) {
return ex && typeof ex === 'object' && 'default' in ex ? ex['default'] : ex;
}
var _classCallCheck = _interopDefault(
require('@babel/runtime/helpers/classCallCheck'),
);
var _createClass = _interopDefault(
require('@babel/runtime/helpers/createClass'),
);
/* eslint-disable import/prefer-default-export */
var Pointer =
/*#__PURE__*/
(function() {
function Pointer(x, y) {
_classCallCheck(this, Pointer);
this.x = void 0;
this.y = void 0;
this.x = x;
this.y = y;
}
_createClass(Pointer, [
{
key: 'move',
value: function move(x, y) {
return new Pointer(this.x + x, this.y + y);
},
},
]);
return Pointer;
})();
exports.Pointer = Pointer;
//# sourceMappingURL=my-ts-lib.cjs.development.js.map
这样的 ES6 语法胶水代码还很多,包括async/await
语法。
polyfill
实践证明,babel 现在在自动引入 polyfill 方面做得还不是特别好。所以 ts-lib-scripts 不会自动插入新 API 的补丁引用。当你的库使用到新的 API,但是又需要支持旧浏览器,你就需要在库的醒目位置(如 README.md 文档)提示使用者需要引入这些新 API 的 polyfill。
tree shaking
ts-lib-scripts 使用 rollup 做打包,所以支持 tree shaking。
tree-shaking 特性会将应用中没有使用到的代码在最终打包时剔除掉。
ts-lib-scripts 使用babel-plugin-annotate-pure-calls插件辅助产生#__PURE__
注释,以使项目充分利用 tree shaking 剔除无用代码。
灵感
日常工作中经常需要创建 TypeScript 库,但是每次都需要从零开始设置配置。用惯create-react-app创建小应用后,创建一个类似的简单易用的工具的想法就变得非常强烈。偶然间遇到tsdx,哇喔,多简洁的工具呀。可惜跟我想要的还差一些距离,而所在的团队又急切需要这方面的工具,所以就出来了这样的一个轮子,满足日常需求。
ts-lib-scripts 不是什么
ts-lib-scripts 只关注如何构建 TypeScript 库,不关注其他方面的需求。你如果想构建 React 应用,请使用create-react-app、Next.js等工具;如果你想构建 JavaScript 库,看看microbundle;如果想构建大型应用,您最好研究一下Webpack。
下一步
ts-lib-scripts 会围绕着 TypeScript 库的开发、构建、部署体验继续前进,与周边工具继续加强关联性,如 VSCode、Git、Github、CircleCI、Docker 等。
近期规划清单:
- 更易用的 ts-lib-scripts 文档
- 支持 React (0.2.0)
- 添加 demo 支持
- 文档支持:docusaurus(针对普通库)
- 文档支持:docz(针对 React 组件库) (0.2.0)
- TypeScript 编译缓存,提升 TypeScript 编译速度
- 默认启用 UMD(正在提升 UMD 打包体验)
- 文档:CircleCI 集成
- Git:提交前检查提交注释(git commit message)
- vscode:添加默认的 demo 调试配置
- vscode:推荐安装的插件清单
-
npm:发布包命令(类似如
lerna publish
) - eject
- 文档:迁移指南
- 其他类型的文件编译处理
中期规划清单:
- 使用 16 倍速的swc编译 TypeScript 代码(等着 swc 的成熟)
- 英文文档
- monorepo 支持
- 分离的 ES Module 支持(不合并文件,只针对单个文件进行编译处理)