共计 5940 个字符,预计需要花费 15 分钟才能阅读完成。
1、“共享组件不间接打包进代码”什么意思?
比方你写了一个组件库,外面有 Button
、Dialog
、MessageBox
3 个组件,其中Dialog
、MessageBox
组件都援用了 Button
组件,那么 Button
组件就是共享组件。当对这 3 个组件挨个打包实现后去查看 Dialog
、MessageBox
组件打包实现后的产物代码,会发现 Dialog
、MessageBox
组件外面都蕴含了 Button
组件产物代码,这样一来产物体积就变大了。
那有没有什么方法使得打包实现后的 Dialog
、MessageBox
组件产物里不蕴含 Button
组件产物代码呢?
答案必定是有的,比方 ant-design-vue
、element-ui
它们打包后的产物代码中就不蕴含共享组件代码,它们是通过 import
或require
去加载共享组件。
如 ant-design-vue
打包产物代码:
2、怎么实现呢?
这个问题也困扰了我很久,直到我用 esbuild
去打包我的【vue3 bootstrap 图标组件库】时我才解决这个问题。
原理很简略:把共享组件当成内部扩大(Externals)
在打包时通常会把 vue
设置为内部扩大,那共享组件为甚么不能设置为内部扩大呢?将共享组件设置为内部扩大后 webpack 或其余打包工具就不会将其打包进产物中,而是以 import
或require
的模式去加载。
看到这里你应该恍然大悟了吧!接下来的代码你就会写了。
3、代码实现(vue3)
我这里以 esbuild
打包为例,webpack 或 vite 代码差不多。
首先须要装置 2 个要害依赖:npm i esbuild esbuild-plugin-vue -D
接下来装置 esbuild 打包进去依赖:npm i esbuild-plugin-progress -D
装置 css 解决依赖:npm i postcss esbuild-sass-plugin autoprefixer postcss-preset-env postcss-import -D
目录构造:
-my-project
+node_modules
-src
-components
yn-button.scss
YnButton.vue
YnDialog.vue
YnMessageBox.vue
App.vue
main.js
build-lib.js
package.json
yn-button.scss
.yn-button{transition: all .3s;}
YnButton.vue
<template>
<button
class="yn-button"
:type="nativeType"
:disabled="disabled">
<slot></slot>
</button>
</template>
<script>
export default {
name: 'YnButton',
props: {
nativeType: {
type: String,
default: 'button'
},
disabled: {
type: Boolean,
default: false
}
}
};
</script>
<style lang="scss">
@import "yn-button";
</style>
YnDialog.vue
<template>
<dialog
v-show="visible"
class="yn-dialog"
:class="{'dialog-opened': visible}":data-opened-count="openCount">
<h1>
<slot name="title">{{title}}</slot>
</h1>
<div class="dialog-content">
<slot></slot>
</div>
<div class="dialog-footer">
<yn-button class="dialog-cancel-btn" @click="close"> 勾销 </yn-button>
<yn-button class="dialog-ok-btn" @click="close"> 确定 </yn-button>
</div>
</dialog>
</template>
<script>
import {
ref,
watch
} from 'vue';
// 因为 esbuild 打包时是以脚本执行目录为根目录,因而这里不以我的项目门路去引入 button 组件
import YnButton from 'src/components/YnButton.vue';
let count = 0;
export default {
name: 'YnDialog',
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
}
},
components: {YnButton},
emits: ['update:visible', 'close'],
setup (props, ctx) {let openCount = ref(count);
watch(() => props.visible, function (isVisible) {
count++;
openCount.value = count;
});
return {
openCount,
close () {ctx.emit('update:visible', false);
ctx.emit('close');
}
};
}
};
</script>
YnMessageBox.vue
<template>
<div class="yn-message-box">
<slot></slot>
<div class="yn-message-box-operate">
<yn-button> 确定 </yn-button>
</div>
</div>
</template>
<script>
import YnButton from 'src/components/YnButton.vue';
export default {
name: 'YnMessageBox',
components: {YnButton}
};
</script>
3.1、惯例打包(共享组件打包进产物)
build-lib.js
const path = require('path');
const vue = require('esbuild-plugin-vue').default; // 解决 vue 组件
const esBuild = require('esbuild');
const progress = require('esbuild-plugin-progress'); // esbuild 打包进度条
// scss、css 解决
const {sassPlugin} = require('esbuild-sass-plugin');
const postcss = require('postcss');
const autoprefixer = require('autoprefixer');
const postcssPresetEnv = require('postcss-preset-env');
const postcssImport = require('postcss-import');
function build (entryPoint) {
esBuild.build({
bundle: true,
entryPoints: [entryPoint],
outdir: 'libs',
external: ['vue'],
loader: {'.ts': 'ts'},
format: 'esm',
drop: ["console", "debugger"], // 移除 console、debugger 信息
treeShaking: true,
plugins: [
sassPlugin({async transform (source) {const { css} = await postcss([
autoprefixer,
postcssPresetEnv(),
postcssImport()]).process(source, { from: undefined});
return css;
}
}),
vue(),
progress()]
});
}
build(path.resolve(__dirname, './src/components/YnButton.vue'));
build(path.resolve(__dirname, './src/components/YnDialog.vue'));
build(path.resolve(__dirname, './src/components/YnMessageBox.vue'));
在控制台执行node ./build-lib.js
,在 libs 目录将会有 6 个打包产物:
有没有发现不对,应该打包出 4 个产物才对,因为 YnDialog、YnMessageBox 组件基本没有增加 css,但打包后却多出了 2 个 css 文件,这就是将共享组件打包进代码的问题之一
再来看下打包后的产物:
YnButton.js
YnButton.css
YnDialog.js
YnDialog.css
通过查看 YnDialog.js
产物的内容,发现外面蕴含了 YnButton
组件的代码,显然这不是咱们想要的
3.2、拆散共享组件代码打包
esbuild
有一个 external
参数能够配置指定的依赖为内部扩大,但它是动态的,只实用于配置一些全局的内部扩大,对于动静的、须要依据条件去判断的内部扩大它是做不到的。
那有什么方法能够做到呢?答案就是:plugin(插件)
咱们能够写一个插件来实现
const path = require('path');
const vue = require('esbuild-plugin-vue').default; // 解决 vue 组件
const esBuild = require('esbuild');
const progress = require('esbuild-plugin-progress'); // esbuild 打包进度条
// scss、css 解决
const {sassPlugin} = require('esbuild-sass-plugin');
const postcss = require('postcss');
const autoprefixer = require('autoprefixer');
const postcssPresetEnv = require('postcss-preset-env');
const postcssImport = require('postcss-import');
function build (entryPoint) {
esBuild.build({
bundle: true,
entryPoints: [entryPoint],
outdir: 'libs',
external: ['vue'],
loader: {'.ts': 'ts'},
format: 'esm',
drop: ["console", "debugger"], // 移除 console、debugger 信息
treeShaking: true,
plugins: [
sassPlugin({async transform (source) {const { css} = await postcss([
autoprefixer,
postcssPresetEnv(),
postcssImport()]).process(source, { from: undefined});
return css;
}
}),
{ // 自定义设置内部扩大 esbuild 插件
name: 'my-esbuild-plugin',
setup (build) {
let filter = /./;
/*
onResolve 钩子是一个门路解析的钩子,是 esbuild 去解析门路的时候触发的,也就是你执行 import ... from ... 的时候会调用 onResolve 钩子
*/
build.onResolve({filter: filter}, function (args) {// console.log('args', args);
let modulePath = args.importer;
let kind = args.kind;
let path = args.path;
let fileInComponents = modulePath.includes('/components/');
// 只有通过导入的,并且在 components 目录下的、以.vue 结尾的文件能力视为内部扩大
let external = fileInComponents && kind === 'import-statement' && path.endsWith('.vue');
// 不须要指定为内部扩大的依赖间接 return null
if (!external) {return null;}
/*
将 import 门路中的 'src/components/' 局部替换成 './',因为打包后它们都是在同一个文件夹。打包后都产物中共享组件将以 import XX from './YY.vue' 模式引入
*/
// tip: 这部分逻辑须要依据本人我的项目目录构造来判断是否须要这样做
if (path.startsWith('src/components/')) {path = path.replace('src/components/', './');
path = path.replace('.vue', '.js');
}
return {
path,
// 是否须要 external (如果该模块为 external,则 esbuild 构建时不会去加载该模块,而是保留原有的 import xxx from 'yyy' 代码)
external
};
});
}
},
vue(),
progress()]
});
}
build(path.resolve(__dirname, './src/components/YnButton.vue'));
build(path.resolve(__dirname, './src/components/YnDialog.vue'));
build(path.resolve(__dirname, './src/components/YnMessageBox.vue'));
再到控制台运行 node ./build-lib.js
命令,此时在 libs 目录下只有 4 个产物了:
再来看下产物代码:
YnButton.js
YnButton.css
YnButton
组件打包产物没有变动,没问题
YnDialog.js
YnDialog
组件打包产物没有再蕴含 YnButton 组件代码,而是通过 import
模式引入,这就是咱们想要的后果。
功败垂成!