Skip to content

Latest commit

 

History

History
480 lines (405 loc) · 18 KB

README.md

File metadata and controls

480 lines (405 loc) · 18 KB

手把手教你如何使用 rollup 打包 ts 工具库

新建项目及工具库结构

  mkdir -p build-lib build-lib/packages/utils/modules

初始化 npm,创建工具库文件

进入 build-lib 项目根目录 cd build-lib

初始化 package.json 文件 npm init -y

utils/modules 文件下创建 format.ts

/** 时间格式化 */
export function formatDate(date: any, format: string = 'YYYY-MM-DD HH:mm:ss') {
  if (!(date instanceof Date)) {
    date = new Date(date)
    if (isNaN(date)) {
      console.error('时间格式错误')
      return ''
    }
  }

  var z = {
    Y: date.getFullYear(),
    M: date.getMonth() + 1,
    D: date.getDate(),
    H: date.getHours(),
    m: date.getMinutes(),
    s: date.getSeconds(),
  }
  return format.replace(/(Y+|M+|D+|H+|m+|s+)/g, (v: string) => {
    return String((v.length > 1 ? '0' : '') + (z as any)[v.slice(-1)]).slice(-(v.length > 2 ? v.length : 2))
  })
}

/** 金钱格式化 */
export function formatNumber(value: number | string, precision = 2, seperator: string = '') {
  if (!value) return 0
  const numStr = typeof value === 'string' ? value.replace(/,/g, '') : `${value || ''}`
  const arr = numStr.split('.')
  let strInt = arr[0]
  let strFractional = arr[1] || ''
  if (seperator) {
    strInt = strInt.replace(/\d{1,3}(?=(\d{3})+$)/g, `$&${seperator}`)
  }
  precision = Math.max(0, precision)
  if (strFractional.length > precision) {
    strFractional = strFractional.substr(0, precision)
  }
  if (strFractional.length > 0) {
    strFractional = strFractional.replace(/0+$/, '')
  }
  if (strFractional.length > 0) {
    return `${strInt}.${strFractional}`
  }
  return strInt
}

在 utils 文件下创建index.ts

/** 导出模块所有方法 */
// export * from "./modules/format"
/** 指定导出方法 */
export { formatDate, formatNumber } from './modules/format'

import { formatDate } from './modules/format'

export function testBuildTarget() {
  const testconstOfVar = 'YYYY-MM-DD'
  return () => formatDate(new Date(), testconstOfVar)
}

安装 rollup 及配置 rollup.config.js

安装 npm i -D rollup rollup-plugin-node-resolve

根目录下创建rollup.config.js

// rollup.config.js
import resolve from 'rollup-plugin-node-resolve'
export default {
  // 入口
  input: 'packages/utils/index.ts',
  output: {
    // 打包后文件
    file: 'dist/utils.js',
    // 编译格式 commonjs umd amd
    format: 'umd',
    name: '@anles/utils',
  },
  plugins: [resolve()],
}

安装配置完成之后,我们就可以打包了, 修改package.json

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "build:utils": "rollup -c",
  "start": "rollup -w -c"
}

运行npm run build:utils,

我们遇到了错误,原来 rollup 并不支持直接打包 ts 文件的,我们需要一个插件rollup-plugin-typescript2

支持 Typescript

安装 npm i -D typescript rollup-plugin-typescript2

rollup.config.js文件中添加插件

import typescript from "rollup-plugin-typescript2";
// 在plugins中添加typescript插件以便支持ts打包js
{
  plugins: [
    typescript()
  ],
}

支持 ts 了,我们需要配置 ts 的一些规则,记得我们安装了 typescript 包,这个时候就要用到它了,运行npx tsc --init,会自动创建tsconfig.json文件,npx 是运行 node_modules 内置包的命令,tsc 是 typescript 的命令.

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */

    /* Basic Options */
    // "incremental": true,                   /* Enable incremental compilation */
    "target": "esnext",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
    "module": "esnext",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    // "lib": [],                             /* Specify library files to be included in the compilation. */
    // "allowJs": true,                       /* Allow javascript files to be compiled. */
    // "checkJs": true,                       /* Report errors in .js files. */
    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    "declarationDir": "lib/types",
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
    "sourceMap": true,                     /* Generates corresponding '.map' file. */
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    // "outDir": "./",                        /* Redirect output structure to the directory. */
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "composite": true,                     /* Enable project compilation */
    // "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */
    // "removeComments": true,                /* Do not emit comments to output. */
    // "noEmit": true,                        /* Do not emit outputs. */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true,                           /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */
    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* Type declaration files to be included in compilation. */
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
    // "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */

    /* Source Map Options */
    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */

    /* Advanced Options */
    "skipLibCheck": true,                     /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true  /* Disallow inconsistently-cased references to the same file. */
  },
  "include": [
    "packages/**/*.ts",
    "packages/**/*.js",
    "rollup.config.js",
    ".eslintrc.js",
    "lib/**.js",
    "lib/**.ts",
  ],
  "exclude": [
    "node_modules"
  ]
}

这个时候我们再运行npm run build:utils,打包成功了,惊喜吧。打开构建的源码,发现语法并没有转译为我们想要的 es5 的版本,毕竟像箭头函数,reset 参数、扩展符在兼容上面并不是很友好,随着发展,这些东西以后可能并不需要转,但是对于现在来说,它是必须的。那提到语法转换,自然是少不了我们的Babel兄弟,下面就让我们来配置它。

Babel

Babel 是一个工具链,主要用于在当前和较旧的浏览器或环境中将 ECMAScript 2015+代码转换为 JavaScript 的向后兼容版本

  • 转换语法
  • 目标环境中缺少的 Polyfill 功能(通过@ babel / polyfill)
  • 源代码转换(codemods)

了解了 babel,接下我们就要开始为build-lib来使用它。

  • 安装npm i -D rollup-plugin-babel @babel/core @babel/preset-env
  • 由于我们需要支持 Typescript, 我们还需要安装npm i -D @babel/preset-typescript

新建.babelrc.json

{
  "presets": ["@babel/preset-env", "@babel/preset-typescript"]
}

rollup.config.js文件中添加插件

import babel from "rollup-plugin-babel";
// 在plugins中添加typescript插件以便支持ts打包js
{
  plugins: [
    babel({
      // 过滤文件
      exclude: "node_modules/**",
    })
  ],
}

新建 .browserslistrc文件,根据浏览器以及系统版本来自动适配 Babel 的 polyfill,用来语法兼容。

ie >= 11
Firefox >= 3.5
chrome  >= 35
opera >= 11.5

大功告成,这个时候我们就可以再次打包,然后检查语法是否能根据.browserslistrc文件自动适配。

然而发现了问题,设置了却无法自动适配, 原来我们在设置了@babel/preset-typescript插件后 babel 会以tsconfig.json的配置为准,但是打包的编译格式决定权却在于rollup.config.js内的format字段,所以得出以下方案

在 javascript 文件打包时

  • 根据rollup.config.js内的output.format字段去更改编译格式
  • 根据.browserslistrc内的版本判断去更改语法版本

在 typescript 文件打包时

  • 根据rollup.config.js内的output.format字段去更改编译格式
  • 根据tsconfig.json内的target字段去更改语法版本

Eslint、prettier

支持代码的类型校验、语法校验,以及代码格式化

  1. 安装依赖
# 安装 eslint 套件
npm i -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

# 安装 prettier 套件
npm i -D prettier eslint-config-prettier eslint-plugin-prettier
  1. 新建eslintrc.js
module.exports = {
  parser: '@typescript-eslint/parser',
  extends: ['prettier/@typescript-eslint', 'plugin:prettier/recommended'],
  parserOptions: {
    project: './tsconfig.json',
    tsconfigRootDir: __dirname,
    ecmaVersion: 2019,
    sourceType: 'module',
  },
  rules: {
    '@typescript-eslint/no-explicit-any': 'off',
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
  },
}
  1. 新建.prettierrc
{
  "printWidth": 120,
  "tabWidth": 2,
  "semi": false,
  "singleQuote": true,
  "endOfLine": "auto",
  "quoteProps": "as-needed",
  "trailingComma": "all"
}
  1. vscode 安装prettiereslint,配置保存自动格式化
    1. 在 vscode 的 setting.json 文件中添加
    "editor.formatOnSave": true, // 每次保存自动格式化
    1. vscode 中设置 prettier 是无效的,所以我们可以在电脑的根目录下(.ssh同级目录)设置全局.prettierrc配置文件, 当项目根目录没有此类文件时,会自动使用全局配置的文件

husky、lint-staged

  • husky 可以防止使用 Git hooks 的一些不好的 commit 或者 push
  • lint-staged 是一个在 git 暂存文件上运行命令的工具。

安装 npm i -D husky lint-staged

修改package.json

"scripts": {
  "lint": "eslint packages --ext .ts,.js"
},
"husky": {
  "hooks": {
    "pre-commit": "lint-staged"
  }
},
"lint-staged": {
  "*.{ts,js}": [
    "eslint --fix",
    "git add"
  ]
}

压缩、配置scripts

  1. 安装依赖
npm i -D rimraf npm-run-all rollup-plugin-uglify lodash.merge
  1. 修改rollup.config.js
import babel from 'rollup-plugin-babel'
import typescript from 'rollup-plugin-typescript2'
import resolve from 'rollup-plugin-node-resolve'
import { uglify } from 'rollup-plugin-uglify'
import merge from 'lodash.merge'
import path from 'path'
import pkg from './package.json'

const extensions = ['.js', '.ts']
const pathResolve = function (...args) {
  return path.resolve(__dirname, ...args)
}
// 打包任务的个性化配置
const jobs = {
  esm: {
    output: {
      format: 'esm',
      file: pathResolve(pkg.module),
    },
  },
  umd: {
    output: {
      format: 'umd',
      file: pathResolve(pkg.main),
    },
  },
  min: {
    output: {
      format: 'umd',
      file: pathResolve(pkg.main.replace(/(.\w+)$/, '.min$1')),
    },
    plugins: [uglify()],
  },
}

// 从环境变量获取打包特征
const mergeConfig = jobs[process.env.NODE_ENV || 'esm']

export default merge(
  {
    input: 'packages/utils/index.ts',
    output: {
      file: 'lib/index.js',
      format: 'umd',
      name: '@anles/utils',
    },
    plugins: [
      resolve({
        extensions,
        modulesOnly: true,
      }),
      typescript({
        useTsconfigDeclarationDir: true,
      }),
      babel({
        exclude: 'node_modules/**',
        extensions,
      }),
    ],
  },
  mergeConfig,
)
  1. package.json添加命令
"main": "lib/index.umd.js",
"module": "lib/index.esm.js",
"module": "lib/types/index.d.js",
"scripts": {
  "lint": "eslint 'src/**/*.{js,ts}'",
  "dev": "rollup -w -c --environment NODE_ENV:esm",
  "build:esm": "rollup -c --environment NODE_ENV:esm",
  "build:umd": "rollup -c --environment NODE_ENV:umd",
  "build:min": "rollup -c --environment NODE_ENV:min",
  "build": "rimraf lib/* && run-p build:esm build:umd build:min"
},

运行npm run build会同时执行三个子编译任务,分别是:

  • build:esm - 编译出符合 esm 规范的可执行文件,供 Vue、React 等采用 esmodule 规范进行模块化打包的项目使用
  • build:umd - 编译出符合 umd 规范的可执行文件,供 jQuery、Vue、NodeJS 等项目使用
  • build:min - 编译出符合 umd 规范的压缩的可执行文件

发布npm

  1. npm login登录npm,没有的话去npm注册一个。
  2. 配置packages的files字段,表示上传哪些文件到npm, 也可以使用.npmignore文件来过滤不上传的文件,建议还是使用files来表示,据说git也会识别.npmignore文件,这样会造成版本库和npm包上传冲突。
"files": [
  "lib/",
  "package.json",
  "README.md"
],
  1. npm publish发布并上传至npm, 需要注意的是,每次发布都需要更改package.jsonversion版本号, 另外在上传名字前缀为@*/*(例如@anles/utils)时, 需要追加npm publish --access public,因为npm默认将带有前缀@*/*的包名,做为私有包,所以你没有私有包的权限是上传不了的。现在我们指定了它不是私有,因此它成功的上传了。

参考文献