阅读6分钟

1. 背景

需求: 将项目作为第三方依赖包的方式,打包并发布到私有npm仓库,提供指定外部系统/框架引入使用(例如场景:当vue框架需要默认携带 element-ui组件), 作为依赖包,需要尽可能压缩打出包的大小

优化思路: 1、打包时排除项目下与引用框架重复的依赖包。2、在引用框架下安装本身就需要的依赖。

项目环境: Vue@2.6.14 webpack@5.87.0

2. 优化步骤

const A = 需要打包的项目

const B = 作为引入方

此次打包有插件 MonacoWebpackPlugin 的需求,不需要时可不用管。

A 使用webpack 的 externals 配置排除外部依赖:
♥♥♥ 为重点修改位置

configureWebpack: config => {
    // 这个插件用于限制输出的代码块数量,将所有模块打包成一个单一的代码块,这在某些情况下可能有助于简化使用。
    const plugins = [
      new webpack.optimize.LimitChunkCountPlugin({
        maxChunks: 1
    config.performance = {
      // 打包文件大小配置
      maxEntrypointSize: 10000000, // 设置入口文件的最大大小限制
      maxAssetSize: 30000000 // 设置单个文件的最大大小限制
    // ♥♥♥ 将新增 插件 合并至plugins配置中,生产打包时不可直接插入 MonacoWebpackPlugin,会导致monaco-editor部分内容被打包
    config.plugins = [
      ...config.plugins,
      ...plugins
    // 生产配置  ♥♥♥ 要改的位置
    if (process.env.NODE_ENV === 'production') {
      config.devtool = false
      // 打包时排除外部依赖包,这意味着在打包时不会将这些库包含在内,而是期望使用者在其自身项目中手动引入它们
      config.externals = {
        /* 以下依赖项目自身单独使用时要注释,避免没有依赖的主框架无法启动项目 */
        /* 打包给其他项目使用时可根据引用方是否有这个依赖来决定是否放开 */
        'monaco-editor': 'monaco-editor',
        'element-ui': 'element-ui',
        'core-js': 'core-js',
        'mockjs': 'mockjs',
        'normalize.css': 'normalize.css',
        'nprogress': 'nprogress',
        'vuex': 'vuex',
        'xcrud': 'xcrud',
        'vue-tippy': 'vue-tippy',
        'vue-i18n': 'vue-i18n',
        'vue': 'vue',
        'vue-router': 'vue-router',
        'pinia': 'pinia'
      // 删除控制台日志输出,通过压缩工具 `terser` 的配置实现
       config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
    } else {
      // ♥♥♥ 非生产环境引入 MonacoWebpackPlugin 插件辅助使用
      config.plugins.push(new MonacoWebpackPlugin())

A 的 package.json 内添加被排除的依赖:
peerDependencies的目的是提示宿主环境去安装满足插件peerDependencies所指定依赖的包,然后在插件import或者require所依赖的包的时候,永远都是引用宿主环境统一安装的npm包,可解决插件与所依赖包不一致的问题。

"peerDependencies": { 
    "monaco-editor": "0.27.0", 
    "element-ui": "^2.15.6", 
    "core-js": "^3.6.5", 
    "mockjs": "^1.1.0", 
    "normalize.css": "^8.0.1", 
    "nprogress": "^0.2.0",
    "vuex": "^3.4.0", 
    "xcrud": "^0.4.19",
    "vue-tippy": "^4.14.0", 
    "vue-i18n": "^8.27.2",
    "vue": "^2.6.14",
    "pinia": "^2.0.23",
    "vue-router": "3.2.0" 

2.2 A项目 配置 package.json 打包

A 执行生产环境配置打包 package.json 下的内容
package.json文件内配置指令为如下:(打包package文件夹内内容并同时 --report 输出分析文件,可直接进入此lib包查找report 文件查看打包文件的大小占比分析)

"lib-web": "vue-cli-service build --mode production --target lib --dest lib --name project-A packages/index.js --report"

命令解析:

  • "lib-web":这是一个自定义的 npm 脚本名称。你可以通过在终端中运行 npm run lib-web 来执行这段命令。
  • vue-cli-service build:这是 Vue CLI 提供的一个命令,用于构建项目。它基于 Vue CLI 提供的配置文件执行构建过程。
  • --mode production:这是构建模式的选项,将构建设定为生产模式,以便进行代码优化和压缩。
  • --target lib:这是构建目标的选项,将构建目标设定为库(Library)。
  • --dest lib:这是构建输出目录的选项,指定构建后的输出文件放置在 lib 目录中。
  • --name project-A:这是库的名称选项,设置库的名称为 project-A,这个名称会在构建后的文件名中体现,例如,如果你将库的名称设置为 project-A,构建后生成的文件名可能会是类似于 project-A.umd.min.js 的形式,这个文件名会在其他项目中引用该库时使用。这个名称的设置通常是由库的开发者根据需要来决定的。它可以用来标识和区分不同的库,避免命名冲突,并且在其他项目中引用时能够清晰地指明是哪个库。。
  • packages/index.js:这是入口文件选项,指定了库的入口文件为 packages/index.js。这是库的主要入口,其他项目在引用这个库时会使用这个入口文件,因此此处文件也决定了我们项目A打包的代码范围。
  • --report:这是生成构建报告的选项,用于在构建结束后生成一个报告,展示构建过程中的资源使用情况和性能指标。
  • // 执行命令打包

    npm run lib-web

    执行完成 项目会在根目录下生产 lib 包
    现在开始发布lib 包到 私有云仓库

    注意!发布时,会同时将lib 包和项目下的package.json一同发布,在第三方项目下载我们这个推送的依赖时,会根据这个package.json 检查这个引用方是否具备定义的依赖,没有的话他会同时下载。所以尽量保证 A 和 B 框架内公用的依赖 版本一致~

    2.3 依赖发布:

    nrm ls 查看nrm 配置
    nrm current 查看当前使用的是哪个源
    nrm add ** 添加npm源,如 http://xxx:3434 可以很方便的切换npm的私有库和共有库了 
    例如: nrm add  test  http://xxx:3434 
    nrm use test 切换源
    nrm del test 删除nrm配置
    要在私有npm仓库中发布包首先需要注册或登录账号。如果我们还没有账号的话,通过输入命令 npm adduser, 然后依次输入用户名,密码即可创建完毕。
    如果已有账号,通过输入命令 npm login,然后依次输入用户名,密码即可登录。然后进入我们需要上传的代码目录,执行命令发布即可;

    (1)注册账号:
    $ npm adduser --registry <http://xxx:3434>

    输入npm账号用户名、密码和邮箱,如下:
    Username: npmtest
    Password:
    Email: (this IS public)npmtest@npmtest
    Logged in as npmtest on http://xxx:3434/.(输出Logged in as npmtest on http://xxx:3434/,表示npm账号npmtest成功登录到http://xxx:3434/私有仓库了)

    (2)发布npm包 (默认会往当前的nrm 源发布,会自动根据项目A package.json 里的配置进行发布,不允许出现重复版本号的依赖,版本号配置位置:"version": "1.1.1")
    $ npm publish --registry <http://xxx:3434> 发布完成后,刷新一下 http://xxx:3434,就可以看到刚才发布的包
    如果版本重复,阔以修改 package.json version

    "name": "test-project", "version": "1.0.1",

    (3)删除已发布的依赖包  
    $ npm unpublish test-project@1.0.1

    2.4 B项目 引入依赖 

    B 项目下引入A依赖 http://xxx:3434/xxxx/test-project-1.0.1.tgz ,并添加被排除但未安装的依赖
    以下依赖只写了部分,正常会根据 A项目之前配置的 peerDependencies 自己下载一次依赖

    "dependencies": {
        "test-project": "http://xxx:3434/xxxx/test-project-1.0.1.tgz"
        "monaco-editor": "0.27.0",
    "devDependencies": {
      "monaco-editor-webpack-plugin": "4.2.0",
    

    2.5 B项目 配置 webpack 的 MonacoWebpackPlugin 插件使用

    const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
    ---------------------------
    config.plugins = [
      ...config.plugins,
      ...plugins,
      new MonacoWebpackPlugin()
    

    2.6 安装依赖 并 启动

    npm i  

    npm run dev

    对第三方B项目引入的组件进行引入验证

    优化排除多余依赖过程流程 结束

    3、优化过程问题与解决方案

    3.1 A 排除的依赖需要 和 使用此依赖的项目B的依赖版本保持一致,否则会还是会重新下载依赖导致依赖冲突。

    3.2 Cannot read properties of undefined (reading 'install')

    vue.runtime.esm.js?2b0e:5120 
    Uncaught TypeError: Cannot read properties of undefined (reading 'install')
    at Vue.use (vue.runtime.esm.js?2b0e:5120:1)
    at 8754 (peoject-A.umd.min.js?3da8:1127:1)

    解:A 内定义的打包的代码范围内,上文介绍的 packages/index.js定义的打包代码范围内,剔除引用了打包范围之外的代码后重新打包可解决

    3.3 排除monaco-editor 依赖后 引入 报错无法加载该文件,需要适配加载器,但实验了很多方法都不生效 (降低依赖版本可解决)

    dependencies:
    "monaco-editor": "0.27.0",
    devDependencies:
    "monaco-editor-webpack-plugin": "4.2.0",
    

    3.4 排除 残留 的 monaco-editor 下的 esm 文件夹。解:打包时不能把插件 MonacoWebpackPlugin  打包进去

    已经在 vue.config.js 中 config.plugins.push(new MonacoWebpackPlugin()) 解决