通用npm包(组件库)打包构建

一、背景

现在的前端开发,绕不开使用一些第三方的NPM包。同时,很多公司不论大小都有了自己的NPM仓库,然后发布一些只有自己使用的私有NPM包。我们在发包的时候,包的代码怎么构建才能体积最小,同时做到新能最好就成了一个非常重要的问题。

在一些发包实现中,无论是UI包还是纯粹的TypeScript Utils包,你当然都可以不打包而直接将代码放到npm包的dist目录中,然后以来业务工程进行构建。但是这显然是有问题的,因为这些NPM包都是比较稳定的代码,不像业务代码那样需要进行频繁的修改和调试了,直接暴露源码给业务使用,会使这些包在不同的项目中多次重复构建,无疑会拉长业务开发时热更新和生产构建时间。

二、如何解决

就像背景中提到的,一般我们的NPM包可以分为两类,分别是:

  • 纯TypeScript(javascript)类型,只包含逻辑代码
  • UI类,包含VUE相关代码的组件,根据vue的2和3的版本不同,又可以细分分别适用于vue2和vue3的两个小类

在分为以上以上两类的同时,还有一种情况需要考虑下,就是有些C端的产品,可能会进行服务端渲染优化。因此我们需要考虑到这些包可能会在Node环境中运行,甚至一些utils包就是直接运行在Node环境中的。所以这个时候我们就需要一份已与CommonJS规范的导出。另外,我们有些工具包还要考虑是否开源出去的情况,如果考虑开源的话可能还需要一份基于UMD规范的导出。

所以,根据以上情况,我们需要多份打包脚本配置。

使用TSC打包Typescript为ESModule、CommonJS及UMD格式

package.json

{
  // 下列内容只包含了打包构建用到的配置,其他字段用到的请自行补充
  "main": "dist/main/index.js",
  "typings": "dist/types/index.d.ts",
  "module": "dist/module/index.js",
  "unpkg": "dist/umd/index.js",
  "exports": {
    ".": {
      "types": "./dist/main/index.d.ts",
      "import": "./dist/module/index.js",
      "require": "./dist/main/index.js",
      "browser": "./dist/umd/index.js"
    }
  },
  "scripts": {
    "prepublish": "pnpm build",
    "build": "run-p build:*",
    "build:main": "tsc -p ./pack/tsconfig.json",
    "build:module": "tsc -p ./pack/tsconfig.module.json",
    "build:umd": "tsc -p ./pack/tsconfig.umd.json",
    "watch:module": "tsc -p ./pack/tsconfig.module.json -w",
    "watch:main": "tsc -p ./pack/tsconfig.json -w"
  },
  "files": [
    "dist",
    "src"
  ],
  "devDependencies": {
    "@types/node":"^18.13.0",
    "typescript": "^4.9.5",
    "npm-run-all": "^4.1.5"
  }
}

1.基本配置&生成基于CommonJS规范的导出

pack/tsconfig.json

{
    "compilerOptions": {
      "target": "ES6", // 编译目标版本
      "outDir": "../dist/main",  // 输出目录
      "rootDir": "../src", // 根目录
      "moduleResolution": "nodenext", // 如何处理模块
      "module": "commonjs", // 生成的模块系统代码
      "declaration": true, // 生成相应的 .d.ts文件
      "declarationDir":"../dist/types", // 生成相应的 .d.ts文件的存放目录
      "inlineSourceMap": true, // 生成单个的sourcemaps文件
      "esModuleInterop": true, // https://www.typescriptlang.org/tsconfig#esModuleInterop
      "resolveJsonModule": true, // 允许导入json文件
      "strict": false,  // 不使用严格模式
      "noUnusedLocals": true, // 若有未使用的局部变量则抛错
      "noUnusedParameters": true,  // 若有未使用的参数则抛错
      "noImplicitReturns": true, // 不是函数的所有返回路径都有返回值时报错
      "noFallthroughCasesInSwitch": true, // 报告switch语句的fallthrough错误。(即,不允许switch的case语句贯穿)
      "traceResolution": false, // 不生成模块解析日志信息
      "listEmittedFiles": false, // 不打印出编译后生成文件的名字。
      "listFiles": false, // 编译过程中不打印文件名。
      "pretty": true, // 给错误和消息设置样式,使用颜色和上下文
      "lib": ["dom"], // 编译过程中需要引入的库文件的列表
      "types": ["node"], // 要包含的类型声明文件名列表
      "typeRoots": ["node_modules/@types", "src/types"], // 要包含的类型声明文件路径列表
    },
    "include": ["../src/**/*.ts"],
    "exclude": ["**/node_modules/**"],
    "compileOnSave": true
  }

2.生成基于ESModule的导出文件

pack/tsconfig.module.json

{
  "extends": "./tsconfig",
  "compilerOptions": {
    "target": "ES2015",
    "outDir": "../dist/module",
    "module": "ES2015",
    "moduleResolution": "nodenext",
    "resolveJsonModule": false, // 允许导入json文件
  },
}

3.生成基于UMD的导出文件

pack/tsconfig.umd.json

{
    "extends": "./tsconfig",
    "compilerOptions": {
      "target": "es5",
      "outDir": "../dist/umd",
      "module": "UMD",
      "moduleResolution": "nodenext",
      "resolveJsonModule": false, // 允许导入json文件
      "lib": ["es5", "dom"], // 编译过程中需要引入的库文件的列表
    }
  }

基于Webpack打包通用UI库(Base Vue2)

工程目录结构

package.json

{
  "name": "private pkg",
  "version": "0.0.1-beta.0",
  "main": "dist/main/index.js",
  "typings": "dist/types/index.d.ts",
  "module": "dist/module/index.js",
  "unpkg": "dist/umd/index.js",
  "exports": {
    ".": {
      "types": "./dist/types/index.d.ts",
      "import": "./dist/module/index.js",
      "require": "./dist/main/index.js",
      "browser": "./dist/umd/index.js"
    }
  },
  "scripts": {
    "build": "run-p build:*",
    "build:esm": "webpack --config pack/webpack.esmodule.conf.js",
    "build:common": "webpack --config pack/webpack.common.conf.js",
    "build:types": "vue-tsc --skipLibCheck --declaration --emitDeclarationOnly --declarationDir dist/types"
  },
  "files": [
    "dist"
  ],
  "devDependencies": {
    "filter": "^0.1.1",
    "unplugin-vue-components": "^0.24.0",
    "vue-template-compiler": "2.7.14",
    "vue-loader": "^15.10.0",
    "vue":"^2.7.0",
    "vue-tsc": "^1.2.0",
    "npm-run-all": "^4.1.5",
	"webpack-node-externals": "^3.0.0",
	"progress-bar-webpack-plugin": "^2.1.0",
	"esbuild-loader": "^4.0.0",
	"webpack-merge": "^5.0.0",
	"css-loader": "^6.8.1",
	"postcss-loader": "^7.3.3",
    "sass-loader": "^13.3.2",
    "style-loader": "^3.3.3",
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "ES6",
    "module": "ES6",
    "moduleResolution": "nodenext",
    "strict": false,
    "sourceMap": true,
    "importHelpers": true,
    "experimentalDecorators": true,
    "noImplicitAny": false,
    "noImplicitThis": false,
    "esModuleInterop": true,
    "jsx": "preserve",
    "jsxFactory": "VueTsxSupport",
    "allowJs": true,
    "declaration": true,
    "declarationDir": "./dist/types",
    "skipLibCheck": true,
    "types": [
      "webpack-env",
      "vue"
    ]
  },
  "include": [
    "shims-vue.d.ts",
    "./index.ts"
  ],
  "exclude": [
    "node_modules/**/*"
  ],
  "vueCompilerOptions": {
    "target": 3
  }
}

webpack.base.conf.js

const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const {VueLoaderPlugin} = require('vue-loader');

const webpackConf = {
    mode: 'production',
    entry: {
        app: ['./index.ts']
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    compilerOptions: {
                        preserveWhitespace: false
                    }
                }
            },
            {
                test: /\.(j|t)sx?$/,
                exclude: /(node_modules)/,
                use: {
                    loader: 'esbuild-loader',
                    options: {
                        loader: 'tsx',
                        target: 'esnext' // Syntax to compile to (see options below for possible values)
                    }
                }
            }
        ]
    },
    optimization: {
        minimize: true
    },
    resolve: {
        extensions: ['.js', '.vue', '.json', '.ts', '.tsx', '.scss'],
        modules: ['node_modules'],
        alias: {
            '@': './src'
        }
    },
    stats: 'normal',
    plugins: [
        new VueLoaderPlugin(),
        new ProgressBarPlugin({
            complete: 'O',
            incomplete: '='
        })
    ]
};
module.exports = webpackConf;

webpack.common.conf.js

const baseWebpackConf = require('./webpack.base.conf.js');
const {merge} = require('webpack-merge');
const nodeExternals = require('webpack-node-externals');
const path = require('path');

const webpackConf = {
    output: {
        path: path.resolve(process.cwd(), './dist/main'),
        filename: `index.js`,
        chunkFilename: '[id].js',
        library: {
            type: 'commonjs2'
        }
    },
    module: {
        rules: [
            {
                test: /\.(sa|sc|c)ss$/i,
                use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
            }
        ]
    },
    externals: [nodeExternals()]
};
module.exports = merge(baseWebpackConf, webpackConf);

webpack.esmodule.conf.js

const baseWebpackConf = require('./webpack.base.conf.js');
const {merge} = require('webpack-merge');
const path = require('path');

const webpackConf = {
    output: {
        path: path.resolve(process.cwd(), './dist/module'),
        filename: `index.js`,
        chunkFilename: '[id].js',
        library: {
            type: 'module'
        }
    },
    experiments: {
        outputModule: true
    },
    module: {
        rules: [
            {
                test: /\.(sa|sc|c)ss$/i,
                use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
            }
        ]
    },
    externals: [
        {
            vue: 'vue',
            'vue-property-decorator': 'vue-property-decorator',
		  	//这里很重要,冗余的其他库都需要在这里配置external掉
        }
    ]
};
module.exports = merge(baseWebpackConf, webpackConf);

webpack.umd.conf.js

const baseWebpackConf = require('./webpack.base.conf.js');
const {merge} = require('webpack-merge');

const webpackConf = {
    output: {
        path: path.resolve(process.cwd(), './dist/module'),
        filename: `${pkgName}.umd.js`,
        chunkFilename: '[id].js',
        library: {
            name: `${pkgName}`,
            type: 'umd',
            export: 'default'
        }
    },
    externals: [
        {
            vue: 'vue'
        }
    ]
};
module.exports = merge(baseWebpackConf, webpackConf);

基于Webpack打包通用UI库(Base Vue3)

工程目录结构同上

package.json

{
  "name": "",
  "version": "0.0.1",
  "main": "dist/main/index.js",
  "typings": "dist/types/index.d.ts",
  "module": "dist/module/index.js",
  "unpkg": "dist/umd/index.js",
  "exports": {
    ".": {
      "types": "./dist/types/index.d.ts",
      "import": "./dist/module/index.js",
      "require": "./dist/main/index.js",
      "browser": "./dist/umd/index.js"
    }
  },
  "scripts": {
    "build": "run-p build:*",
    "build:esm": "webpack --config pack/webpack.esmodule.conf.js",
    "build:common": "webpack --config pack/webpack.common.conf.js",
    "build:types": "vue-tsc --skipLibCheck --declaration --emitDeclarationOnly --declarationDir dist/types"
  },
  "files": [
    "dist"
  ],
  "devDependencies": {
    "@vue/compiler-sfc": "^3.2.47",
    "filter": "^0.1.1",
    "unplugin-vue-components": "^0.24.0",
    "vue": "^3.2.47",
    "vue-loader": "^17.0.0",
    "vue-tsc": "^1.2.0",
	"npm-run-all": "^4.1.5",
	"webpack-node-externals": "^3.0.0",
	"progress-bar-webpack-plugin": "^2.1.0",
	"esbuild-loader": "^4.0.0",
	"webpack-merge": "^5.0.0",
	"css-loader": "^6.8.1",
	"postcss-loader": "^7.3.3",
    "sass-loader": "^13.3.2",
    "style-loader": "^3.3.3",
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "ES6",
    "module": "ES6",
    "moduleResolution": "nodenext",
    "strict": false,
    "sourceMap": true,
    "importHelpers": true,
    "experimentalDecorators": true,
    "noImplicitAny": false,
    "noImplicitThis": false,
    "esModuleInterop": true,
    "jsx": "preserve",
    "jsxFactory": "VueTsxSupport",
    "allowJs": true,
    "declaration": true,
    "declarationDir": "./dist/types",
    "skipLibCheck": true,
    // "baseUrl": ".",
    // "outDir": "./dist/types",
    "types": [
      "webpack-env",
      "vue"
    ]
  },
  "include": [
    "shims-vue.d.ts",
    "./index.ts"
  ],
  "exclude": [
    "node_modules/**/*"
  ],
  "vueCompilerOptions": {
    "target": 3
  }
}

webpack.base.conf.js

const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const {VueLoaderPlugin} = require('vue-loader');

const webpackConf = {
    mode: 'production',
    entry: {
        app: ['./index.ts']
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    compilerOptions: {
                        preserveWhitespace: false
                    }
                }
            },
            {
                test: /\.(j|t)sx?$/,
                exclude: /(node_modules)/,
                use: {
                    loader: 'esbuild-loader',
                    options: {
                        loader: 'tsx',
                        target: 'esnext' // Syntax to compile to (see options below for possible values)
                    }
                }
            }
        ]
    },
    optimization: {
        minimize: false
    },
    resolve: {
        extensions: ['.js', '.vue', '.json', '.ts', '.tsx', '.scss'],
        modules: ['node_modules'],
        alias: {
            '@': './src'
        }
    },
    stats: 'normal',
    plugins: [
        new VueLoaderPlugin(),
        new ProgressBarPlugin({
            complete: 'O',
            incomplete: '='
        })
    ]
};
module.exports = webpackConf;

webpack.common.conf.js

const baseWebpackConf = require('./webpack.base.conf.js');
const {merge} = require('webpack-merge');
const nodeExternals = require('webpack-node-externals');
const path = require('path');

const webpackConf = {
    output: {
        path: path.resolve(process.cwd(), './dist/main'),
        filename: `index.js`,
        chunkFilename: '[id].js',
        library: {
            type: 'commonjs2'
        }
    },
    module: {
        rules: [
            {
                test: /\.(sa|sc|c)ss$/i,
                use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
            }
        ]
    },
    externals: [nodeExternals()]
};
module.exports = merge(baseWebpackConf, webpackConf);

webpack.module.conf.js

const baseWebpackConf = require('./webpack.base.conf.js');
const {merge} = require('webpack-merge');
const path = require('path');

const webpackConf = {
    output: {
        path: path.resolve(process.cwd(), './dist/module'),
        filename: `index.js`,
        chunkFilename: '[id].js',
        library: {
            type: 'module'
        }
    },
    experiments: {
        outputModule: true
    },
    module: {
        rules: [
            {
                test: /\.(sa|sc|c)ss$/i,
                use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
            }
        ]
    },
    externals: [
        {
            vue: 'vue' 
		  // 这里很重要,冗余的其他库都需要在这里配置external掉
        }
    ]
};
module.exports = merge(baseWebpackConf, webpackConf);

webpack.umd.conf.js

const baseWebpackConf = require('./webpack.base.conf.js');
const {merge} = require('webpack-merge');

const webpackConf = {
  output:{
    path: path.resolve(process.cwd(), './dist/module'),
    filename: `${pkgName}.umd.js`,
    chunkFilename: '[id].js',
    library:{
      name:`${pkgName}`,
      type:'umd',
      export: 'default',
    }
  },
  externals:[{
    vue: 'vue'
  }]
}
module.exports = merge(baseWebpackConf,webpackConf)

文章看到这里了,如果你觉得对你的打包工作有启发的话,请点个赞吧

#webpack##架构开发##npm##组件库#
全部评论

相关推荐

#牛客AI配图神器#1. 背景在现代软件开发中,为了提高效率和质量,自动化工作流成为一种不可或缺的实践。利用 GitHub 和 GitLab 等平台的 CI/CD 工具,开发者可以自动化代码构建、测试、部署等一系列流程,从而减少手动操作带来的错误,提高软件交付的速度与可靠性。2. 原理自动化工作流通常由用户定义的 YAML 文件来描述,其中定义了工作流的触发条件、各个任务的执行步骤以及环境配置。以下是 GitHub 和 GitLab 自动化工作流的基本原理:触发器:工作流可以配置为在特定事件后触发,例如代码提交、拉取请求创建或定时任务等。作业和步骤:工作流由多个作业(jobs)组成,每个作业包含若干步骤(steps),步骤可以是安装依赖、运行测试、部署等命令。环境:可以在特定的环境中运行作业,例如使用 Docker 容器、特定的操作系统等。3. 特点GitHub Actions 特点简单易用:使用 YAML 文件描述工作流,学习曲线较低。社区支持:拥有大量可重用的 Action,可以快速构建复杂的工作流。集成方便:与 GitHub 生态无缝集成,支持版本控制、问题跟踪和项目管理等功能。GitLab CI/CD 特点内置集成:直接在 GitLab 中进行配置,无需额外设置。丰富的功能:支持多种 CI/CD 功能,如管道 (Pipelines)、作业 (Jobs)、环节 (Stages) 等。灵活性:支持多种环境和部署策略,适应不同的项目需求。https://www.nowcoder.com/issue/tutorial?zhuanlanId=j572L2&uuid=a21ffae20b9049fe997c1829a8605772
点赞 评论 收藏
分享
03-25 07:55
顺丰集团_HR
顺丰集团2025届春招+26届实习内推!【公司简介】:顺丰集团是世界500强企业第377位,中国第一大、世界第四大快递物流服务商。【岗位】:开发、算法、商业数据分析、管培生、国际管培生、人力、财务、大数据、运营星计划、企划、菁英计划-Marketing、产品经理、产品运营、项目管理、经营管理岗、航空类(不限专业不卡学校,每人可同时投递2个职位)【工作城市】:深圳、武汉、上海、广州、湖南、重庆、成都等全国各地均有岗【薪酬福利】:有竞争力的薪资、五险一金、法定假期、通讯补贴、兴趣社团、弹性福利、医食住教行福利包、节日礼包、开工利是、体检、健康驿站、绩优福利、绩优假、绩优活动【内推链接】https://campus.sf-express.com/m/?channel=29&referCode=7BJ5G5#/newGraduatesList【内推码】7BJ5G5(招聘信息获取渠道选择“校园大使推荐”,加速进面,有问题随时回复~) #春招#                                         #实习#                                         #内推#                                         #秋招#                     
点赞 评论 收藏
分享
Time line3.12 一面 hr面3.13 二面 技术面3.17 三面 技术面3.24 oc一面主要就是聊聊天,介绍公司基本情况以及薪资待遇(正常来说,这不应该三面么)二面1.简单介绍一下你自己2.拷打项目,几种常见卷积算法的优缺点3.写算子时有碰到bank conflict吗?为什么会发生bank confict以及如何解决?4.说一下CPU和GPU的架构5.说一下你对grid,block,thread的理解6.写算子时如何最大化地利用缓存?迭代一次的数据尽量符合L1的大小,整个程序的数据尽量符合L2的大小。(当时我的回答)7.你知道线程束分歧吗?(warp divergent 也叫线程束分化)8.手撕矩阵乘算子(当时我打开vscode,他看了我写的reduce以及conv2d,于是便叫我写一个矩阵乘)第一次技术面,有点紧张,在面试官的提示下顺利写出naive版本,然后说自己对后面的优化,以及如何确定最佳分块大小。过程中还问了blockDim.x和gridDim.x最大能开多少。反问环节三面感觉和二面差不多,主要也是拷打项目不同点:1.共享内存和cache的区别2.你了解Tensor core吗?它和CUDA core比加速矩阵乘谁更快?3.你了解Transformer吗?4.softmax算法在深度学习中的应用5.手撕softmax算子(有了经验后,十分顺利)反问环节最后祝大家都能顺利找到实习#面经##实习##算子开发##CUDA#
查看14道真题和解析
点赞 评论 收藏
分享
评论
1
2
分享

创作者周榜

更多
牛客网
牛客企业服务