抽离 Npm 包发布

4 阅读3分钟

前言

本文是个人学习实践过程中的记录及理解,如有错漏欢迎指出。

相关知识来源于哲玄前端(抖音ID:44622831736)大前端全栈实践课程

目标

使用方引入 npm 包后,调用暴露出去的 API 快速启动项目,将本地文件一并加载到该系统中运行。

要点

Loader 解析异常问题

npm 包提供了一些内置的 Loader,由于 Webpack 默认会在本地项目根目录找 loader,由于本地目录中不一定安装了该 Loader 导致报错,所以我们应该 告诉 Webpack 如何找到这些 Loader

这里可以有 两种方式

  • 使用 Node.js 的原生方法 require.resolve()require 查找的起点是当前执行代码的文件所在目录 (即 __dirname),从而找到 npm 包中相关的 Loader,找到后会返回该 loader 的绝对路径。 Webpack 拿到这个绝对路径后,就不会再去本地的 node_modules 里瞎找,直接加载该路径文件。

  • webpack.config.js 中修改 resolveLoader 配置。提供数组告诉 Webpack 有哪些兜底策略,相比于前一种方式更方便,一劳永逸,但解析顺序取决于数组中定义的顺序。

    resolveLoader: {
        modules: [
          'node_modules',
          path.resolve(__dirname, 'node_modules') // npm 包中的 node_modules 做兜底
        ]
    }
    

资源加载问题

由于该包中暴露的 API 运行时会将本地项目文件一起打包到系统中运行,而本地项目或许涉及到某些依赖未安装的问题。为了解决这种情况的发生,可以通过 Webpack 配置一份别名供用户使用,同时也是告诉 Webpack 从哪里获取依赖,也能避免重复安装依赖。

vue

resolve: {
  alias: {
    vue: require.resolve("vue"),
    moment: require.resolve("moment")
  },
},

需要注意的是,为了避免直接写死 vue 始终指向自身 node_modules 目录,路径应该先用 动态探测 方式查看。

// 获取本地项目里的 Vue 路径
const localProjectVuePath = path.resolve(process.cwd(), 'node_modules/vue');
​
// 动态判断应该用哪个 Vue
const vuePathToUse = fs.existsSync(localProjectVuePath)
  ? localProjectVuePath         // 优先使用宿主项目的 Vue
  : require.resolve('vue');     // 兜底:使用 npm 包自身的 Vue

文件拓展

借助前文提到的文件解析内核 elpis-core ( 类 egg 理念,约定大于配置 ) 的能力,将约定位置的文件全部解析挂在到系统上,这能轻松解决大部分系统文件在本地完成拓展的问题。

可是,当涉及到"识别前端文件并提供给 npm 包中的其他文件使用" 的情况,光靠内核是无法实现的,还需要借助 Webpack 的能力。

这会有三种方式可以实现:

  • 约定固定位置放置:告诉 npm 包使用方,如需为前端组件、页面添加拓展文件,固定放置在某种格式的文件列表中(如 app/pages/dashboard/components/[componentName]),以供系统读取。

    const businessModules = require.context(
      "$businessPath/app/pages", // $businessPath 已在 webpack 中添加别名 process.cwd()
      false,
      /.js$/,
    );
    
  • 约定统一 config 文件:与上一中方法不同的是,约定放置的不再是每个文件固定的位置,而是通过约定位置的 config 文件告诉运行方:“需要拓展的文件放置在哪里”。

    const businessModules = require.context(
      "$businessPath/app/pages", // $businessPath 已在 webpack 中添加别名 process.cwd()
      false,
      /page.config.js$/,
    );
    

    以便系统直接读取文件,而不再需要为了添加一个文件嵌套过多的文件夹,将文件放置的格式交还给使用方决定。

  • 暴露拓展 API:类似于 app.use 的实现,在系统启动前通过暴露给使用方的 API 添加拓展文件。对于使用方而言,更加方便快捷,但需要更多的前置操作,每个有可能拓展的位置都需留下钩子。