突然接到一个需求,改造某个项目的打包产品,本来会生成多个文件,现在要求修正为只需一个ESM
文件。
定睛一看,是个Vite
项目,Vite
的版本是v5.0.10
,vite.config.ts
是这样的:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'node:path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
name: 'property',
fileName: 'property'
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue'
}
}
}
}
})
这个与Vite
官方推荐的库模式差不多,应该是参阅装备的。
现状
打包后的dist
目录如下:
dist
|-- abap-936FyI36.js
|-- ...
|-- ...
|-- property.js
|-- property.umd.cjs
|-- ...
|-- ...
|-- style.css
|-- ...
|-- ...
`-- yaml-GSgOAuiZ.js
0 directories, 81 files
咱们看到,property.umd.cjs
中,是全量的代码。本来引证它是不错的,可是需求是要一份ESM
代码,不是UMD
的。
UMD
这里略微解释下什么是UMD
,年轻的朋友可能不认识它。
UMD
,全称为Universal Module Definition
,即通用模块界说,是一种JavaScript
模块界说的规范。它的方针是使一个模块的代码在各种模块加载器或者没有模块加载器的环境下都能正常运行。
UMD完成了这个方针,经过查看存在的JavaScript
模块体系并适应性地供给一个模块界说。假如AMD
(如RequireJS
)存在,它将界说一个AMD模块,假如CommonJS
(如Node.js
)存在,它将界说一个CommonJS
模块,假如都不存在,那么它将界说一个大局变量。
咱们以一个名为AI
的包为例,以下便是UMD
的中心代码:
(function (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['AI'] = factory()
} else {
root['AI'] = factory()
}
})(this, function () {
// AI的业务代码
})
这种方式使得你的模块能够在各种环境中运用,包含浏览器和服务器。
UMD
是ESM
(ECMAScript Modules
)出现前的产品,它当然不可能兼容ESM
,而因为ESM
的特别性,UMD
也做不到兼容ESM
。
ESM
咱们再看property.js
文件,它是标准的ESM
,内容是这样的:
import { i as f } from "./index-o0DaZxYG.js";
export {
f as default
};
dist
目录下生成了这么多的文件,是因为默许状况下,将node_modules
里的包都生成了一个JavaScript文件。
下来,咱们一步步处理这个需求。
计划
合并node_modules
先把node_modules
中的文件合并成一个。这涉及到Vite
的分包战略。
这时,为rollupOptions
中装备manualChunks
即可:
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue'
},
manualChunks: (id: string) => {
if (id.includes('node_modules')) {
return 'vendor'
}
}
}
}
不好,竟然报错了:
error during build:
RollupError: Invalid value for option “output.manualChunks” – this option is not supported for “output.inlineDynamicImports”.
咱们到Rollup文档里找下inlineDynamicImports
:
这显着是说锅是UMD
的,inlineDynamicImports
与manualChunks
是冲突的,不能一起存在。Vite
底层处理UMD
时估量装备了这个选项。咱们到Vite
的GitHub源码里也找到这段,验证了咱们的想法:
这是说UMD
和IIFE
(Immediately Invoked Function Expression
,立即调用函数表达式)以及SSR
的某种条件下,会开启这个选项。
正好咱们也不需要UMD
,就把Vite
的默许选项替换掉,只需要添加formats
即可:
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
name: 'property',
fileName: 'property',
formats: ['es', 'cjs']
},
这样只会生成ESM
和CommonJS
两种了:
dist
|-- property.cjs
|-- property.js
|-- style.css
|-- vendor-T520oB1z.js
`-- vendor-XYPt9q-o.cjs
0 directories, 5 files
这个称号咱们未必满意,能够进行一次修正:
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
name: 'property',
formats: ['es', 'cjs'],
- fileName: 'property',
fileName: (format) => `property.${format}.js` // 打包后的文件名
},
这样,新的文件名便是这样了:
dist
|-- property.cjs.js
|-- property.es.js
|-- style.css
|-- vendor-T520oB1z.js
`-- vendor-XYPt9q-o.cjs
0 directories, 5 files
假如你喜欢.mjs
、.cjs
的后缀,也都是能够的。
合并vendor
咱们的需求是只需一个主JS文件,那么还不能要vendor
,怎么办呢?
很简单:
manualChunks: (id: string) => {
- if (id.includes('node_modules')) {
return 'vendor'
- }
}
这样,就将所有文件都合并成一个JS了。
dist
|-- property.cjs.js
|-- property.es.js
`-- style.css
0 directories, 3 files
至于函数返回的字符串是什么,就无所谓了。
合并style.css
咱们的组件的款式都在style.css
中,还需要用户单独引进,多少不便。
Vite
官方并没有供给相关的插件或装备项。
这个看着像,可是可能只针对外部的chunk,咱们这种状况未生效。
运用插件
我在上找到了这篇文章《完成一个打包时将CSS注入到JS的Vite插件》:
大佬便是凶猛,帮咱们造好了轮子。
不过当我翻开大佬留的库房,竟然变成只读了:
好吧,究竟这篇文章是22年的了。
好在大佬说明晰归档的原因,本来强中自有强中手,大佬安利了另一个插件:
于是,咱们只需要引证这个插件就能够了:
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default defineConfig({
plugins: [
vue(),
cssInjectedByJsPlugin()
],
build: {
}
})
这次只会生成2个文件:
dist
|-- property.cjs.js
`-- property.es.js
0 directories, 2 files
咱们来看下文件中榜首行注入的内容(以下是展开后的):
(function () {
"use strict";
try {
if (typeof document < "u") {
var A = document.createElement("style");
A.appendChild(
document.createTextNode(
'@charset "UTF-8";:root{--el-color-white:#ffffff;}'
// style.CSS的内容
)
),
document.head.appendChild(A);
}
} catch (e) {
console.error("vite-plugin-css-injected-by-js", e);
}
})();
其实便是往HTML的head
中注入了这一段大局的CSS。
咱们再仔细看这一句typeof document < "u"
很有意思,typeof document
有两种可能的值,当它存在时是object
,不存在是undefined
,object < u
,undefined > u
,所以typeof document < "u"
相当于是typeof document === "object"
或者typeof document !== "undefined"
。这是JS代码紧缩的一种特别手法,咱们平常要是写出这样的代码必定要给人骂死。
功德圆满!
TIPS
其实,假如不考虑必须内嵌CSS的话,有个更简洁的办法能够这样处理:
rollupOptions: {
external: ['vue'],
output: {
intro: 'import "./style.css";',
manualChunks: (id: string) => {
return 'vendor'
}
}
}
这个intro
与banner是等价的装备项,表示往bundle后的代码的头部注入一段信息,与之对应的是outro
和footer
。
// rollup.config.js
export default {
...,
output: {
...,
banner: '/* my-library version ' version ' */',
footer: '/* follow me on Twitter! @rich_harris */'
}
};
这样,生成的JS文件中就有了以下内容:
var Ea = (s, e, t) => (Bg(s, typeof e != "symbol" ? e "" : e, t), t);
import "./style.css";
import { getCurrentScope, onScopeDispose, unref, getCurrentInstance, onMounted, nextTick, watch, ref, defineComponent, openBlock, createElementBlock, createElementVNode, warn, computed, inject, isRef, shallowRef, onBeforeUnmount, onBeforeMount, provide, mergeProps, renderSlot, toRef, onUnmounted, useAttrs as useAttrs$1, useSlots, withDirectives, createCommentVNode, Fragment, normalizeClass, createBlock, withCtx, resolveDynamicComponent, withModifiers, createVNode, toDisplayString as toDisplayString$1, normalizeStyle, vShow, cloneVNode, Text as Text$1, Comment, Teleport, Transition, readonly, onDeactivated, vModelRadio, createTextVNode, reactive, toRefs, onUpdated, withKeys, vModelText, pushScopeId, popScopeId, renderList } from "vue";
...
当用户引进这个JS时,就会动态引证CSS了。
当然,前提是这个JS是要被Vite
或Webpack
等打包工具处理的,假如在HTML里直接引进,尽管网络里下载到了这个CSS文件,但浏览器校验它不是JavaScript,仍是要报错的。
修正package.json
你是不是以为万事大吉了?慢着,别着急走。
因为咱们修正了打包后生成的文件名,别忘了修正package.json
中对应的这几项装备:
{
"main": "./dist/property.cjs.js",
"module": "./dist/property.es.js",
"exports": {
".": {
"import": "./dist/property.es.js",
"require": "./dist/property.cjs.js"
}
}
}
总结
本文介绍了怎么将Vite
项目中的lib库房打包为一个JavaScript
文件,供给了具体的过程和代码示例,包含怎么合并node_modules
、修正Vite
装备以及运用插件进行款式注入等,假如修正了文件名,记得修正package.json
中对应的装备。
最终修正的代码如下:
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default defineConfig({
plugins: [
...,
cssInjectedByJsPlugin(),
],
build: {
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
name: 'property',
formats: ['es', 'cjs'],
fileName: (format) => `property.${format}.js` // 打包后的文件名
},
rollupOptions: {
output: {
manualChunks: (id: string) => {
return 'vendor'
}
}
}
}
})