Code前端首页关于Code前端联系我们

使用RollupJs从头开始构建自己的vue组件库

terry 2年前 (2023-09-08) 阅读数 255 #Vue

前言

目前前端打包工具主要有webpackrollup、、

Gulp是一个面向任务的自动化构建工具。

Webpack是目前最流行的前端资源模块化管理和打包工具。它可以将众多离散的模块按照依赖关系和规则封装成适合生产环境部署的前端资源。您还可以共享按需加载的模块的代码,并在实际需要时异步加载它们。

Rollup 是一个 JavaScript 模块捆绑器,可将小代码片段组装成大而复杂的代码片段,例如库或应用程序。

Rollup 使用 ES6 版本 JavaScript 中包含的新标准化代码模块格式,而不是以前的临时解决方案(如 CommonJS 和 AMD)。 ES6 模块可让您免费、无缝地访问您喜爱的库中最有用的独立功能,而无需您的项目携带未使用的代码。 ES6 模块最终将在浏览器中原生实现,但现在 Rollup 可以让您提前体验。

简单来说,Gulp 适合小型项目,并且是围绕流程构建的; webpack适合大型应用项目,分模块按需加载; Rollup适合构建工具库和优化代码。目前vue和react UI框架都是使用rollup进行压缩的,部分UI框架也使用rollup进行压缩。

本文的思路是指构建Element3,它结合了vue3 + Rollup + ts + Gulp

前期工作

使用 npm init 初始化项目,然后安装必要的 npm 包:

// 初始化
npm init

// 安装必要的 rollup 的 npm 包
yarn add -D
    rollup-plugin-vue                // 类似于 webpack 的vue-loader,vue 组件的加载器
    rollup-plugin-scss               // scss 解析插件
    rollup-plugin-peer-deps-external // 打包的时候用来排除 package.json 内 peerDependencies 字段内的包
    @rollup/plugin-node-resolve      // 使用Node解析算法定位模块
    @rollup/plugin-commonjs          // CommonJS模块转换
    @rollup/plugin-json              // json 文件解析
    @rollup/plugin-replace           // 在打包文件时替换文件中的字符串
    @rollup/plugin-babel             // 转译 JavaScript 新特性
    rollup-plugin-typescript2        // 用于解析ts文件,并生成 x.d.ts 类型文件
    rollup-plugin-terser             // 包最小化生成,也就是压缩
    
// 还需要安装 @vue/compiler-sfc @babel/core typescript rollup 
    
// package.json
{
  "peerDependencies": { // 打包将排除vue
    "vue": "^3.0.11"
  }
}
 

在根目录下新建tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "jsx": "preserve",
    "declaration": true,
    "importHelpers": true,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "allowJs": true,
    "lib": ["esnext", "dom", "dom.iterable", "scripthost"],
    "outDir": "./",
    "resolveJsonModule": true
  },
  "include": ["src", "packages"],
  "exclude": ["node_modules"]
}
 

rollup.config.js

然后在根目录下新建rollup.config.js

// 引入 rollup 相关的包
import pkg from './package.json'
import vuePlugin from 'rollup-plugin-vue'
import scss from 'rollup-plugin-scss'
import peerDepsExternal from 'rollup-plugin-peer-deps-external'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import replace from '@rollup/plugin-replace'
import babel from '@rollup/plugin-babel'
import ts from 'rollup-plugin-typescript2'
import { terser } from 'rollup-plugin-terser'

// 先定义一个 base 配置
// 创建打包文件的头部信息
const createBanner = () => {
  return `/*!
  * ${pkg.name} v${pkg.version}
  * (c) ${new Date().getFullYear()} test
  * @license ISC
  */`
}

// 创建基础配置
const createBaseConfig = () => {
  return {
    input: 'src/index.ts', // 加载入口
    external: ['vue'],
    plugins: [ // 插件加载
      peerDepsExternal(),
      vuePlugin({
        css: true
      }),
      ts(),
      babel({
        exclude: 'node_modules/**',
        extensions: ['.js', '.jsx', '.vue'],
        babelHelpers: 'bundled'
      }),
      resolve({
        extensions: ['.vue', '.jsx', '.js']
      }),
      commonjs(),
      json(),
      scss()
    ],
    output: {
      sourcemap: false,
      banner: createBanner(),
      externalLiveBindings: false,
      globals: {
        vue: 'Vue'
      }
    }
  }
}
 

然后定义不同的打印格式和其他配置:

// 生成文件名
function createFileName(formatName) {
  return `dist/taxreview.${formatName}.js`
}

// es-bundle
const esBundleConfig = {
  plugins: [
    replace({
      preventAssignment: true,
      __DEV__: `(process.env.NODE_ENV !== 'production')`
    })
  ],
  output: {
    file: createFileName('esm-bundler'),
    format: 'es'
  }
}

// cjs
const cjsConfig = {
  plugins: [
    replace({
      preventAssignment: true,
      __DEV__: true
    })
  ],
  output: {
    file: createFileName('cjs'),
    format: 'cjs'
  }
}
// cjs.prod
const cjsProdConfig = {
  plugins: [
    terser(),
    replace({
      preventAssignment: true,
      __DEV__: false
    })
  ],
  output: {
    file: createFileName('cjs.prod'),
    format: 'cjs'
  }
}
// 其它的格式还有 es-browser global 等
 

不同环境的不同封装

// 生产
const prodFormatConfigs = [
  esBundleConfig,
  cjsConfig,
  cjsProdConfig
]
const devFormatConfigs = [esBundleConfig] // 开发
 

最后运行压缩程序

function mergeConfig(baseConfig, configB) {
  const config = Object.assign({}, baseConfig)
  // plugin
  if (configB.plugins) {
    baseConfig.plugins.push(...configB.plugins)
  }

  // output
  config.output = Object.assign({}, baseConfig.output, configB.output)

  return config
}
function createPackageConfigs() {
  return getFormatConfigs().map((formatConfig) => {
    return mergeConfig(createBaseConfig(), formatConfig)
  })
}

function getFormatConfigs() {
  return process.env.NODE_ENV === 'development'
    ? devFormatConfigs
    : prodFormatConfigs
}

export default createPackageConfigs()
 

Vue 组件处理

在根目录下新建一个package文件夹来存放所有组件,并在package内新建一个theme chalk文件夹来存放scss文件。不要直接在vue文件中写入scss文件,然后在theme-chalk中使用scss文件。用碗来包装。

// packages/Test/src/Test.vue
<template>
  <div>
    {{msg}}
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'

export default defineComponent({
  name: 'test',
  setup() {
    let msg = ref('test')

    return { msg }
  }
})
</script>
 
// packages/Test/index.ts
import Test from './src/Test.vue'

/* istanbul ignore next */
Test.install = function (app) {
  app.component(Test.name, Test)
}

export { Test }
 

然后在根目录src/index.ts运行整个程序启动

// src/index.ts
import { Test } from '../packages/Test'

import { version } from '../package.json'
import { setupGlobalOptions } from './globalConfig' // 这个文件用来设置这个ui库的一些全局属性

const components = [
  Test
]

// 这个是用 vue.use 用来注册全局组件的
const install = (app, opts = {}) => {
  app.use(setupGlobalOptions(opts))

  components.forEach((component) => {
    app.use(component)
  })

  applyOptions(app)
}

// 这个是用来挂载像 Message 这样用 js 来调用的组件
function applyOptions(app) {
  app.config.globalProperties.$test = Test
}

const taxreview = {
  version,
  install
}

// 这个是用来单独引用的
export {
  Test,
  install
}

export default taxreview
 
// src/globalConfig.ts  用来设置ui库的一些全局属性
import { getCurrentInstance } from 'vue'

/**
 * get globalOptions $TAXREVIEW config object
 */
export function useGlobalOptions() {
  const instance = getCurrentInstance()

  if (!instance) {
    console.warn('useGlobalOptions must be call in setup function')
    return
  }

  return instance.appContext.config.globalProperties.$TAXREVIEW || {}
}

export function setupGlobalOptions(opts: any = {}) {
  return (app) => {
    app.config.globalProperties.$TAXREVIEW = {
      size: opts.size || '',
      zIndex: opts.zIndex || 2000
    }
  }
}
 

我们还需要编辑package.json

使用这里的线程结构来包裹vue组件

image.pngimage.png

css处理

我们所有的CSS都放在packages/theme-chalk/src下,这样我们就可以在packages/theme-chalk中构建碗包

// packages/theme-chalk/gulpfile.js
'use strict'

const { series, src, dest } = require('gulp')
const sass = require('gulp-sass')
const autoprefixer = require('gulp-autoprefixer')
const cssmin = require('gulp-cssmin')

// 将 scss 文件转译成 css 文件并压缩放入同级的 lib 文件夹内
function compile() {
  return src('./src/*.scss')
    .pipe(sass.sync())
    .pipe(
      autoprefixer({
        overrideBrowserslist: ['ie > 9', 'last 2 versions'],
        cascade: false
      })
    )
    .pipe(cssmin())
    .pipe(dest('./lib'))
}

// 用来拷贝字体文件
function copyfont() {
  return src('./src/fonts/**').pipe(cssmin()).pipe(dest('./lib/fonts'))
}

exports.build = series(compile, copyfont)
 

然后再添加一条命令来处理根目录下package.json中的scss文件

// cp-cli A B: 命令是将路径 A 的文件全部拷贝到路径 B
{
  "build:theme": "node scripts/generateCssFile.js && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk"
}

// yarn build:theme
 

终于

最后,我们使用npm的publish功能将包发布到npm,之后我们可以使用yarn add xxx功能将其应用到项目中。

GitHub:github.com/554246839/r…主分支

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门