✏️
ougege
  • README
  • Docs
    • index
    • Articles
      • AI
        • 体验Chrome AI
        • 体验Cloudflare Workers AI
        • 体验deepseek
      • CSS
        • CSS优化-PurgeCSS
        • 实用效果
        • 开发常用样式
      • Deepin
        • deepin20安装mysql
        • deepin使用tensorflow入门机器学习
        • deepin安装cuda和cuDNN
        • deepin安装lamp
        • deepin安装nvidia驱动
        • deepin安装oh my Zsh
        • deepin安装p7zip
        • deepin换源
        • 安装deepin系统后要做的事
      • Docker
        • CI/CD搭建配置
        • deepin搭建docker环境
        • docker安装和使用gitlab
        • docker搭建nginx+php环境
      • Essay
        • IOS申请邓白氏编码
        • Markdown-Mermaid
        • Markdown Use
        • webview白屏的问题查找和修复
        • 前端开发对接问题和解决办法汇总
        • 国务院机构改革方案
        • 国家级智库
        • 实用网站推荐
        • 常用Markdown数学公式语法
        • 强烈推荐前端要安装的vscode扩展
        • 新建销售计划-页面卡死问题分析
        • 海淘入坑手册
        • 竞品研究
        • 足球知识速成
      • Git
        • GitBook安装和常用命令
        • GitKraken免费版本
        • Git安装和配置
        • Git异常处理
        • Git Worktree使用
        • 前端工程化相关的实用git命令
      • JS
        • ESM模块导出方式对比
        • Emoji多端统一处理
        • JS发布订阅模式
        • JS性能优化
        • JS标准内置对象
        • JS链式调用原理
        • Promise介绍和使用
        • Range的使用
        • Vue+Oauth登录实现
        • Vue实现富文本插入Emoji
        • chrome扩展入门
        • es5新特性
        • es6常用特性
        • es常用片段
        • uniapp使用eslint校验代码
        • 与移动端通信
        • 优秀js库moment
        • 使用vue-socketio
        • 实现一个中间件
        • 小程序webview调试
        • 常用snippets
        • 常用正则
        • 常用的设计模式
        • 微信jssdk封装使用
        • 浏览器宏任务和微任务
        • 浏览器的5种Observer
        • 深入理解赋值、浅拷贝、深拷贝
        • 解析vue指令clickoutside源码
        • 键盘事件与KeyBoardWrapper交互
        • 高德地图常用方法封装
        • 高阶函数片段
      • Network
        • 使用Lighthouse分析前端性能
        • 前后端启用https
        • 宝塔nginx安装ngx_brotli
        • 比较gz与br加载速度
        • 浏览器https提示不安全
        • 浏览器提示HSTS
        • 简单使用tcpdump
        • 静态资源gzip优化
      • Node
        • CommonJS模块导出方式对比
        • Taro command not found 多平台解决方案
        • koa使用和API实现
        • node安装报错Unexpected-token
        • 使用nvm和nrm
        • 使用uniapp给小程序添加云函数
        • 使用verdaccio搭建本地npm仓库
        • 使用vue-cli搭建vue项目
        • 安装Node.js和npm配置
        • 编译成cjs和mjs的思路解析
        • 让你的npmPackage同时支持cjs和mjs
        • 通过GithubAction将内容部署到vps
      • Python
        • Python源管理
        • Python版本管理
        • mitmproxy抓包
        • 微信公众平台开发爬坑经历
      • Shell
        • Ubuntu安装deepin桌面环境
        • Ubuntu安装flatpak软件
        • Ubuntu安装wireshark
        • Ubuntu常见问题汇总
        • dell G3装系统无法识别第二块硬盘
        • linux下virtualbox用gho还原系统
        • mysql常用命令
        • navicat连接一键集成环境的mysql
        • nginx常用命令
        • pm2常用命令
        • virtualbox虚拟机和宿主机互相复制粘贴
        • vps内资源通过mega快传到本地
        • vps报错temporary failure in name resolution
        • vscode修改文件监控数
        • windows+linux双系统引导修复
        • zsh常用插件和命令
        • 一键搭建ChatGPT web版
        • 使用V2ray,CloudFlare Warp解锁GPT
        • 使用vscode进行java开发
        • 利用zx和SSHKey发布代码到服务器
        • 反爬虫一些方案总结和尝试
        • 安装1Panel
        • 安装Bt面板
        • 安装Ubuntu22.04后要做的事
        • 无显示器linux设置默认分辨率
        • 特别实用的shell命令
        • 解决linux安装xmind缺少依赖报错
      • Standards
        • CSS格式化之stylelint
        • CSS规范
        • HTML规范
        • JS规范
        • commit规范
        • 使用husky+commitlint规范代码提交
        • 使用semantic-release自动管理版本号
        • 命名规范
        • 图片规范
        • 版本编号规范
      • Wall
        • 科学上网-Cloudflare-Pages
        • 科学上网-Cloudflare-Warp
        • 科学上网-Geph
        • 科学上网-RackNerd
        • 科学上网-Slicehosting
        • 科学上网-Surfshark
        • 科学上网-Tor
        • 科学上网-XX-NET
        • 科学上网-heroku
        • 科学上网-shadowsock
        • 科学上网-v2ray使用
        • 科学上网-v2ray搭建
        • 科学上网-浏览器代理
        • 科学上网-让终端走代理
      • Windows
        • SourceTree破解免登录(windows版)
        • git bash交互提示符不工作
        • nexus 7 2013 wifi 刷机
        • tree命令生成文件目录
        • 利用charles抓包app
        • 安装Openssl
        • 安装msi文件报错2503和2502
        • 神器vimium使用说明
        • 自用host
        • 解决win10扩展出来的屏幕模糊
        • 解决安装Adobe Air时发生错误
    • Snippets
      • zsh
        • docker
        • extract
        • git-commit
        • git
        • mysql-macports
        • npm
        • nvm
        • pip
        • pm2
        • systemd
        • ubuntu
        • vscode
Powered by GitBook
On this page
  • 介绍
  • 问题场景
  • 解决思路
  • 目录说明
  • 实现
  • 最后
  • 参考

Was this helpful?

  1. Docs
  2. Articles
  3. Node

让你的npmPackage同时支持cjs和mjs

介绍

CJS(CommonJS)是一种用于在服务器端和旧版浏览器中组织和导入/导出 JavaScript 代码的模块系统, 后缀名为 .cjs。 它使用 require 和 module.exports 语法来导入和导出模块。CJS 模块是同步加载的,这意味着在导入模块时,代码会阻塞执行直到模块加载完成。

MJS(ESM,ECMAScript 模块)是 ECMAScript 6(ES6)引入的官方 JavaScript 模块系统, 后缀名为 .mjs。它使用 import 和export 语法来导入和导出模块。MJS 模块是异步加载的,这意味着在导入模块时,代码会继续执行,而不会阻塞。

CJS 和 MJS 之间的主要区别在于语法和加载方式。CJS 适用于服务器端和旧版浏览器,而 MJS 适用于现代浏览器和支持 ES6 模块的环境。

问题场景

让我们看下这段来自 util.js 的代码

:::code-group

const { qs } = require('@muyi086/qs') // [!code warning]
const { varType } = require('@muyi086/var-type') // [!code warning]
const authHttp = require('./authHttp')
const apiUser = require('./api/api_user')
const basicHttp = require('./common/basicHttp')
const uniUtil = require('./common/uniUtil')
const publicUtil = require('./common/publicUtil')
const { abortAllRequest, isAllRequestOver } = basicHttp
const { commonViewTap, getCurrentPageUrlWithArgs, getMiniPage } = uniUtil
const { newTimeStamp, formatDate, renderTodayTomorrowSoOn, transWeekDay, formatDateTime, randomStrFromCharCode } = publicUtil
const { config } = require('./configAll')
const storage = require('./storage')
const { stringify, parse } = qs

:::

::: warning 注意 高亮的俩行是引入的外部 CJS 模块,也就是发布时 npm package 导出的方式为 module.exports = xxx

由于我们不能在一个 js 同时使用 require 和 import, 于是看到后面的其他封装类库的引入都变成了 require 形式 ,这样不太方便,因为在 vue 项目里我是希望有些封装的函数能直接操作 vue 实例和 store 的,但是他们都仅支持 import 导入, 这样我 util 里封装的函数就被迫以只能通过参数的形式传递 vue 实例和 store :::

解决思路

我们将 npm 包编译成 cjs 和 mjs 各一份,用户在 import 时,就返回 mjs 文件,如果是使用 require, 就返回 cjs 文件 ,这样在不同的场景就可以灵活使用了

目录说明

拿我其中一个 @muyi086/var-type 的包举例, 这是项目目录结构

  1. lib目录存放核心代码, 后续用 esbuild 将 index.ts 文件编译成 cjs 和 mjs 两份

  2. package.json 和 .npmignore 分别存放项目配置和忽略文件

  3. readme.md 保存文档说明,LICENSE 保存开源协议

实现

其中最重要的是 package.json 和 esbuild 编译的配置

  1. package.json 我们增加如下代码, 这样我们通过 require 和 import 就会导入不同的文件

    {
      "main": "./lib/index.min.cjs",
      "module": "./lib/index.min.mjs",
      "exports": {
        ".": {
          "require": "./lib/index.min.cjs",
          "import": "./lib/index.min.mjs"
        }
      }
    }
  2. esbuild 编译 index.ts 成 cjs 和 mjs 两份

具体代码如下

::: code-group

/**
 * @Description: js变量类型判断
 * @Author: MuYi086
 * @Email: 1258947325@qq.com
 * @Blog: https://github.com/MuYi086/blog
 * @Date: 2021/04/11 12:30
 */
class VarType {
  private typeList: string[]
  private static _instance: VarType | null = null
  constructor() {  
    this.typeList = ['Null', 'Undefined', 'Object', 'Array', 'ArrayBuffer', 'String', 'Number', 'Boolean', 'Function', 'RegExp', 'Date', 'FormData', 'File', 'Blob', 'URLSearchParams', 'Set', 'WeakSet', 'Map', 'WeakMap']
    this.init()
  }
  static get instance(): VarType {  
    if (!VarType._instance) {
      VarType._instance = new VarType()
    }
    return VarType._instance
  }
  /**
   * 判断变量类型
   * @param {string} value
   * @returns lowercase string
   */
  private type (value: any): string {
    const s = Object.prototype.toString.call(value)
    return s.match(/\[object (.*?)\]/)[1].toLowerCase()
  }

  /**
   * 增加判断类型数据方法
   */
  private init(): void {
    const self = this
    this.typeList.forEach((t: string) => {
      Object.defineProperty(VarType.prototype, `is${t}`, {
        value: function (o: any) {
          return self.type(o) === t.toLowerCase()
        },
        writable: true,
        configurable: true
      })
    })
  }
  /**
   * isBuffer
   * @param {any} val
   * @returns boolean
   */
  static isBuffer(val: any): boolean {  
    return val !== null && (VarType as any).isUndefined(val) && val.constructor !== null && (VarType as any).isUndefined(val.constructor) && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val)
  }

  /**
   * isStream
   * @param {any} val
   * @returns boolean
   */
  static isStream(val: any): boolean {  
    return (VarType as any).isObject(val) && (VarType as any).isFunction(val.pipe)
  }

  /**
   * proxy对象有俩个特性: reactive, readonly
   * @param {*} val
   * @returns boolean
   */
  // isProxy (val) {
  //   function shouldBeCloneable (o) {
  //     const type = typeof o
  //     return (
  //       o?.constructor === ({}).constructor ||
  //       type === 'undefined' ||
  //       o === null ||
  //       type === 'boolean' ||
  //       type === 'number' ||
  //       type === 'string' ||
  //       o instanceof Date ||
  //       o instanceof RegExp ||
  //       o instanceof Blob ||
  //       o instanceof File ||
  //       o instanceof FileList ||
  //       o instanceof ArrayBuffer ||
  //       o instanceof ImageData ||
  //       o instanceof ImageBitmap ||
  //       o instanceof Array ||
  //       o instanceof Map ||
  //       o instanceof Set
  //     )
  //   }
  //   function isCloneable (val) {
  //     try {
  //       postMessage(val, '*')
  //     } catch (error) {
  //       // 错误码25表示不能被clone
  //       if (error?.code === 25) return false
  //     }
  //     return true
  //   }
  //   const _shouldBeCloneable = shouldBeCloneable(val)
  //   const _isCloneable = isCloneable(val)
  //   return _shouldBeCloneable && !_isCloneable
  // }
}
// 使用 varType["isNull"](null)等
export const varType = VarType.instance
{
  "name": "@muyi086/var-type",
  "version": "1.0.5",
  "description": "JS variable type checking",
  "main": "./lib/index.min.cjs", // [!code focus]
  "module": "./lib/index.min.mjs", // [!code focus]
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "exports": { // [!code focus]
    ".": { // [!code focus]
      "require": "./lib/index.min.cjs", // [!code focus]
      "import": "./lib/index.min.mjs" // [!code focus]
    } // [!code focus]
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://git@github.com/MuYi086/npm_package.git"
  },
  "keywords": [
    "typeof",
    "variable",
    "JavaScript",
    "type",
    "var",
    "checking"
  ],
  "author": "MuYi086",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/MuYi086/npm_package/issues"
  },
  "homepage": "https://github.com/MuYi086/npm_package#readme"
}
const { build } = require('esbuild')
const path = require('path')
const chalk = require('chalk')

/**
 * 执行build命令
 * @param {*} inputFilePath 入口文件
 * @param {*} outputPath 输出文件
 * @param {*} format 格式: esm, cjs
 * @param {*} platform 运行平台: node, browser, neutral
 * @param {*} minify // 压缩代码
 * @param {*} bundle // 打包成单个文件
 * @returns promise
 */
const excuteBuild = (inputFilePath, outputPath, format = 'cjs', platform = 'neutral', minify = true, bundle = true) => {
  return build({
    entryPoints: [path.resolve(inputFilePath)],
    outfile: path.resolve(outputPath),
    format,
    platform: platform,
    minify,
    bundle,
  })
}

/**
 * 编译脚本
 */
const compileScript = async () => {
  const varTypeInputFilePath = 'var-type/lib/index.ts'
  const varTypeOutputCjsPath = 'var-type/lib/index.min.cjs'
  const varTypeOutputMjsPath = 'var-type/lib/index.min.mjs'
  try {
    console.log(chalk.blue('-------------------------编译/var-type----------------------'))
    await excuteBuild(varTypeInputFilePath, varTypeOutputCjsPath, 'cjs')
    await excuteBuild(varTypeInputFilePath, varTypeOutputMjsPath, 'esm')
    console.log(chalk.green('编译完成,全部成功!'))
  } catch (e) {
    console.error(chalk.red('编译失败', e))
    process.exit(1)
  }
}

compileScript()

:::

最后

将 package 到 npmjs 后,我们就可以通过包名下载使用了

# 发布包
npm publish --access=public
# 安装包
npm install @muyi086/var-type
// require
const { varType } = require('@muyi086/var-type')
// import
import { varType } from '@muyi086/var-type'

参考

Previous编译成cjs和mjs的思路解析Next通过GithubAction将内容部署到vps

Last updated 10 months ago

Was this helpful?

varType_tree

一文搞懂 cjs 和 mjs 如何相互使用
发布一个 ESM 和 CJS 并存的 package