打包第三库那些事

介绍

一般来说,写完一个第三方库需要打包出三个文件夹的文件,对应三种不同模块类型

# outputpath
├── dist  # umd module
├── es    # es module
├── lib   # commonjs module

三个模块类型

umd

  • UMD(Universal Module Definition)是 AMD 和 CommonJS 的糅合,跨平台的解决方案
  • UMD 打包出来的文件可以直接通过 script 插件 html 中使用
  • 我们的代码会被这样一段代码包裹起来
;(function webpackUniversalModuleDefinition(root, factory) {
  if (typeof exports === 'object' && typeof module === 'object')
    module.exports = factory()
  else if (typeof define === 'function' && define.amd) define([], factory)
  else if (typeof exports === 'object') exports['A'] = factory()
  else root['A'] = factory()
})(window, function() {
  //...
})

commonjs

  • CommonJS 模块是对象,是运行时加载,运行时才把模块挂载在 exports 之上(加载整个模块的所有),加载模块其实就是查找对象属性。
  • 导出使用 module.exports,也可以 exports。就是在此对象上挂属性。exports 指向 module.exports,即 exports= module.exports
  • 加载模块通过 require 关键字引用
module.exports.add = function add() {
  return
}
exports.sub = function sub() {
  return
}

const a = require('a.js')

es module

  • ES Module 不是对象,是使用 export 显示指定输出,再通过 import 输入。此法为编译时加载,编译时遇到 import 就会生成一个只读引用。等到运行时就会根据此引用去被加载的模块取值。所以不会加载模块所有方法,仅取所需。
export const m = 1

export {
  m
}

import { m } from 'a.js'

why

  • umd 为了支持使用者通过各种不同的模块类型引用,包括通过 script 使用第三方库
  • commonjs 支持使用者在 commonjs 模块类型下引用,而且可以部分使用第三方库,不会引入整个库
  • es module 支持使用者在 es module 模块类型下引用, 如果使用者需要对第三方库再打包时,webpack、rollup 都对 es module 有特殊的优化,只打包使用到的方法

使用 webpack 打包 umd 模块

如果使用 webpack 来打包 umd 文件,我们应该配置哪些选项

webpack config

const config = {
  entry,
  output: {
    path: './dist',
    libraryTarget: 'umd',
    filename: 'index.min.js',
    library: 'MyLibrary',
    libraryExport: 'default'
  },
  externals: {
    react: {
      root: 'React',
      commonjs2: 'react',
      commonjs: 'react',
      amd: 'react'
    }
  }
}
  • libraryTarget

    指定打包文件的模块类型
  • library

    如果生成的输出文件,是在 HTML 页面中作为一个 script 标签引入,则变量 MyLibrary 将与入口文件的返回值绑定
  • libraryExport

    默认 webpack 会把返回值绑定在 MyLibrary 的 default 属性下, 也就是MyLibrary.default才是我们模块的返回值。通过设置libraryExport: 'default',起到MyLibrary = MyLibrary.default的效果, 不需要再通过 default属性去访问返回值
  • externals

    为了缩小打包文件的体积,对引用到的其他库的文件,应该过滤掉,比如在这里引用到了 react,但实际上我们并不需要把 react 一起打包进去,我们可以通过一些方式来从 node_modules 文件夹中或者全局变量中访问到 react
// webpack 会判断不同的环境,并以不同的方式去访问react
;(function webpackUniversalModuleDefinition(root, factory) {
  if (typeof exports === 'object' && typeof module === 'object')
    module.exports = factory(require('react'))
  else if (typeof define === 'function' && define.amd)
    define(['react'], factory)
  else if (typeof exports === 'object')
    exports['MyLibrary'] = factory(require('react'))
  else root['MyLibrary'] = factory(root['React'])
})

最后再配置一下 babel-loader,就能生成我们期望的 umd 文件了

使用 babel 打包 commonjs 模块

使用 babel 打包就很简单了, 先看一下 babelrc 怎么写

babelrc

{
  presets: [['@babel/env', { loose: true, modules: 'cjs' }], '@babel/preset-react'],
  plugins: [
    ['@babel/plugin-transform-runtime', { useESModules: false }],
  ]
}

loose

一句话解释: true 的时候代码更现代化代码量少,false 更兼容代码量也更多

modules

设置使用不同的模块类型,commonjs 的话,就设置成'cjs'

transform-runtime

babel 会给每个编译的文件插入一些辅助方法,如果文件一多的话,就出现了很多重复代码,这个插件会改为从第三方包引用辅助方法,
需要额外安装@babel/runtime

e.g.

var _interopRequireDefault = require('@babel/runtime/helpers/interopRequireDefault')

build

babel src --out-dir lib

使用 babel 打包 ES Module 模块

只要把上面里所有 module 相关的设置改成 es module 就行了

babelrc

{
  presets: [['@babel/env', { loose: true, modules: false }], '@babel/preset-react'],
  plugins: [
    ['@babel/plugin-transform-runtime', { useESModules: true }],
  ]
}

build

babel src --out-dir es

最后

安利一波个人写的打包库的工具 build-my-package, js, ts都支持哦!:)

相关推荐