Encapsulate Vue JS component library

1, Introduction

  • Dealing with the boundary of components: knowledge points about components
  • Rapid prototyping provided in Vue cli: it is convenient for us to develop components and run single file components separately
  • Component development: step bar, form component
  • The best way to develop components Storybook: isolate the development components to facilitate the management of components and component testing
  • Monorepo: a way of organizing the project, which is conducive to managing the project structure of the component library
  • Plop: in order to quickly develop components, use plop to generate the basic structure of components based on templates
  • How Lerna + yarn workspaces works: it is convenient to manage package dependencies and submit and publish all components
  • Component testing: use js to test components
  • Rollup packaging

2, Dealing with component boundary problems

  • $ r o o t : stay m a i n . j s in plus enter d a t a number according to , son group piece can through too root: in main Add data into JS, and the sub components can Root: in main Add data data into JS, and the sub components can be accessed through root Data accesses and modifies data. Small projects are more convenient to use, and too many states are difficult to maintain
//main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
  render: h => h(App),
  data: {
    title: 'Root instance - Root'
  },
  methods: {
    handle () {
      console.log(this.title)
    }
  }
}).$mount('#app')

// root.vue
<template>
  <div>
    <!--
      In small applications, you can vue Shared data in root instance
      Components can be $root Access root instance
    -->
    $root.title: {{ $root.title }}
    <br>
    <button @click="$root.handle">obtain title</button>&nbsp;&nbsp;
    <button @click="$root.title = 'Hello $root'">change title</button>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>
  • $ p a r e n t : father son group piece Inlay set , can stay son group piece in through too Parent: parent-child components are nested and can be passed in child components Parent: parent-child components are nested, which can be used in child components through parent Data obtains and modifies the data of the parent component, which can also be used in the sub components of the sub component p a r e n t . parent. parent.parent.data gets the parent component data and modifies it, but props is not allowed to modify it
    This method is too nested and difficult to maintain
// parent.vue
<template>
  <div class="parent">
    parent
    <child></child>
  </div>
</template>

<script>
import child from './02-child'
export default {
  components: {
    child
  },
  data () {
    return {
      title: 'Get parent component instance'
    }
  },
  methods: {
    handle () {
      console.log(this.title)
    }
  }
}
</script>

<style>
.parent {
  border: palegreen 1px solid;
}
</style>
// child.vue
<template>
  <div class="child">
    child<br>
    $parent.title: {{ $parent.title }}<br>
    <button @click="$parent.handle">obtain $parent.title</button>
    <button @click="$parent.title = 'Hello $parent.title'">change $parent.title</button>
  
    <grandson></grandson>
  </div>
</template>

<script>
import grandson from './03-grandson'
export default {
  components: {
    grandson
  }
}
</script>

<style>
.child {
  border:paleturquoise 1px solid;
}
</style>
// grandson.vue
<template>
  <div class="grandson">
    grandson<br>
    $parent.$parent.title: {{ $parent.$parent.title }}<br>
    <button @click="$parent.$parent.handle">obtain $parent.$parent.title</button>
    <button @click="$parent.$parent.title = 'Hello $parent.$parent.title'">change $parent.$parent.title</button>
  </div>
</template>

<script>
export default {
}
</script>

<style>
.grandson {
  border:navajowhite 1px solid;
}
</style>
  • $ c h i l d r e n : father group piece in can can Save stay many individual son group piece , because this children: there may be multiple child components in the parent component, so Children: there may be multiple child components in the parent component, so children is an array, which can be accessed through $children [0] Data obtains the data of the first sub component and modifies it. This method is not flexible and requires index. It is generally used to obtain the data of all sub components
// PARENT.VUE
<template>
  <div>
    <children1></children1>
    <children2></children2>

    <button @click="getChildren">Get subcomponents</button>
  </div>
</template>

<script>
import children1 from './02-children1'
import children2 from './03-children2'
export default {
  components: {
    children1,
    children2
  },
  methods: {
    getChildren () {
      console.log(this.$children)
      console.log(this.$children[0].title)
      console.log(this.$children[1].title)

      this.$children[0].handle()
      this.$children[1].handle()
    }
  }
}
</script>

<style>

</style>
// children1.vue
<template>
  <div>children1</div>
</template>

<script>
export default {
  data () {
    return {
      title: 'children1 Get subcomponents - title'
    }
  },
  methods: {
    handle () {
      console.log(this.title)
    }
  }
}
</script>

<style>

</style>
// children2.vue
<template>
  <div>children2</div>
</template>

<script>
export default {
  data () {
    return {
      title: 'children2 Get subcomponents - title'
    }
  },
  methods: {
    handle () {
      console.log(this.title)
    }
  }
}
</script>

<style>

</style>
  • $ r e f s : discharge stay universal through H T M L mark sign upper can through too refs: it can be placed on ordinary HTML tags through Refs: it can be placed on ordinary HTML tags through refs*** Operate the DOM and put it on the sub component to obtain the values and methods defined by the sub component
// parent.vue
<template>
  <div>
    <myinput ref="mytxt"></myinput>

    <button @click="focus">Get focus</button>
  </div>
</template>

<script>
import myinput from './02-myinput'
export default {
  components: {
    myinput
  },
  methods: {
    focus () {
      this.$refs.mytxt.focus()
      this.$refs.mytxt.value = 'hello'
    }
  }
  // mounted () {
  //   this.$refs.mytxt.focus()
  // }
}
</script>

<style>

</style>
// myinput.vue
<template>
  <div>
    <input v-model="value" type="text" ref="txt">
  </div>
</template>

<script>
export default {
  data () {
    return {
      value: 'default'
    }
  },
  methods: {
    focus () {
      this.$refs.txt.focus()
    }
  }
}
</script>

<style>

</style>
  • $ p r o v i d e / provide/ provide/inject: obtain the members in the parent component through dependency injection. First provide the dependent value in the parent component through the provide function, and then obtain the value in the child component through inject, but the value injected through inject cannot be modified
// PARENT.VUE
<template>
  <div class="parent">
    parent
    <child></child>
  </div>
</template>

<script>
import child from './02-child'
export default {
  components: {
    child
  },
  provide () {
    return {
      title: this.title,
      handle: this.handle
    }
  },
  data () {
    return {
      title: 'Parent component provide'
    }
  },
  methods: {
    handle () {
      console.log(this.title)
    }
  }
}
</script>

<style>
.parent {
  border: palegreen 1px solid;
}
</style>
// CHILD.VUE
<template>
  <div class="child">
    child<br>
    title: {{ title }}<br>
    <button @click="handle">obtain title</button>
    <button @click="title='xxx'">change title</button>
    <grandson></grandson>
  </div>
</template>

<script>
import grandson from './03-grandson'
export default {
  components: {
    grandson
  },
  inject: ['title', 'handle']
}
</script>

<style>
.child {
  border:paleturquoise 1px solid;
}
</style>
// GRANDSON.VUE
<template>
  <div class="grandson">
    grandson<br>
    title: {{ title }}<br>
    <button @click="handle">obtain title</button>
    <button @click="title='yyy'">change title</button>
  </div>
</template>

<script>
export default {
  inject: ['title', 'handle']
}
</script>

<style>
.grandson {
  border:navajowhite 1px solid;
}
</style>

2, Attrs listeners

$attrs binds the non prop attribute in the parent component to the internal component
l i s t e n e r s hold father group piece in of D O M yes as of primary living matter piece Bind set reach within Department group piece stay father group piece in pass genus nature to son group piece , as r e q u i r e d , p l a c e h o l d e r etc. , Silence recognize meeting stay son group piece of root element element of upper add plus , if need plus reach his he element element upper , be need want stay son group piece in need want add plus of element element add plus v − b i n d = " listeners bind the native events of the DOM object in the parent component to the internal component, and pass the attributes in the parent component to the child component, such as required and placeholder. By default, they will be added to the root element of the child component. If they need to be added to other elements, they need to add v-bind to the elements to be added in the child component=“ listeners bind the native events of the DOM object in the parent component to the internal component, and pass attributes to the child component in the parent component, such as required and placeholder. By default, they will be added to the root element of the child component. If they need to be added to other elements, they need to add V − bind="attrs" to the element to be added in the child component, so that attributes can be added to the element. Similarly, If you add v-on to this element=“ l i s t e n e r s " , be can take father group piece pass of square method enter that 's ok transfer use , suitable use to father group piece pass enter many individual square method Time , no use write many individual "listeners", you can call the methods passed by the parent component, which is applicable to multiple methods passed by the parent component without writing multiple methods listeners ", you can call the method passed by the parent component. It is applicable to the event that the dom element itself does not need to write multiple emit s when the parent component passes in multiple methods

// parent.vue
<template>
  <div>
    <!-- <myinput
      required
      placeholder="Enter your username"
      class="theme-dark"
      data-test="test">
    </myinput> -->


    <myinput
      required
      placeholder="Enter your username"
      class="theme-dark"
      @focus="onFocus"
      @input="onInput"
      data-test="test">
    </myinput>
    <button @click="handle">Button</button>
  </div>
</template>

<script>
import myinput from './02-myinput'
export default {
  components: {
    myinput
  },
  methods: {
    handle () {
      console.log(this.value)
    },
    onFocus (e) {
      console.log(e)
    },
    onInput (e) {
      console.log(e.target.value)
    }
  }
}
</script>

<style>

</style>
// myinput.vue
<template>
  <!--
    1. The attribute passed from the parent component to the self defined child component, if not prop receive
       Automatically set to the outermost label inside the subcomponent
       If it is class and style If so, the outermost labels will be merged class and style 
  -->
  <!-- <input type="text" class="form-control" :placeholder="placeholder"> -->

  <!--
    2. If the child component does not want to inherit the non information passed in by the parent component prop Property, you can use inheritAttrs Disable inheritance
       Then pass v-bind="$attrs" Transfer external non prop Property to the desired label

       But that won't change class and style
  -->
  <!-- <div>
    <input type="text" v-bind="$attrs" class="form-control">
  </div> -->


  <!--
    3. Registration event
  -->

  <!-- <div>
    <input
      type="text"
      v-bind="$attrs"
      class="form-control"
      @focus="$emit('focus', $event)"
      @input="$emit('input', $event)"
    >
  </div> -->


  <!--
    4. $listeners
  -->

  <div>
    <input
      type="text"
      v-bind="$attrs"
      class="form-control"
      v-on="$listeners"
    >
  </div>
</template>

<script>
export default {
  // Props: ['placeholder', 'style', 'class'] / /' style ',' class' is reserved attribute setting, and an error will be reported
  // props: ['placeholder']
  inheritAttrs: false
}
</script>

<style>

</style>

3, Rapid prototyping

VueCli provides plug-ins for rapid prototype development and global installation of @ Vue / cli service global
Use vue serve to quickly view the running effect of components,

  • vue serve if no parameter is specified, it will find main in the current directory by default js,index.js,APP. vue,app. Vue as entry file
  • You can also specify the path followed by the component to be loaded
    • vue serve ./src/login.vue

4, Rapid prototyping - ElementUI

Install ElementUI

  • Initialize package json
    • npm init -y
  • Install ElementUI
    • vue add element in addition to installing elementUI, babel and some dependent plug-ins will also be installed in package Generate the necessary configuration in JSON
  • Load the elementUI and use Vue Use() install plug-ins
    new Vue({...})

5, Component development - step bar component

Classification: third party component, basic component and business component

// STEPS.VUE
<template>
  <div class="lg-steps">
    <div class="lg-steps-line"></div>
    <div
      class="lg-step"
      v-for="index in count"
      :key="index"
      :style="{ color: active >= index ? activeColor : defaultColor }"
    >
      {{ index }}
    </div>
  </div>
</template>

<script>
import './steps.css'
export default {
  name: 'LgSteps',
  props: {
    count: {
      type: Number,
      default: 3
    },
    active: {
      type: Number,
      default: 0
    },
    activeColor: {
      type: String,
      default: 'red'
    },
    defaultColor: {
      type: String,
      default: 'green'
    }
  }
}
</script>

<style>

</style>

// STEPS-TEST.VUE
<template>
  <div>
    <steps :count="count" :active="active"></steps>
    <button @click="next">next step</button>
  </div>
</template>

<script>
import Steps from './Steps.vue'
export default {
  components: {
    Steps
  },
  data () {
    return {
      count: 4,
      active: 0
    }
  },
  methods: {
    next () {
      this.active++
    }
  }
}
</script>

<style>

</style>

6, Component development - form component

Overall structure: Form, FormItem, Input and Button



1. slot is written inside each component. When calling, you can add content in the tag and add relevant attributes in props according to the example
2. Nesting problem: because stFormItem must be a child element of stForm, dependency injection can be used in stForm and stFormItem to obtain the values of model and rules in stForm, so as to complete form verification; stInput may be a child element of stFormItem, or it can be used alone. Therefore, it is not suitable for the dependency injection method to obtain the value of the parent component. You can use the while method to obtain its parent element. If the parent element has a name of stInputItem, you can obtain the label and prop of the parent component

<template>
  <div>
    <input v-bind="$attrs" :type="type" :value="value" @input="handleInput">
  </div>
</template>

<script>
export default {
  name: 'LgInput',
  inheritAttrs: false,
  props: {
    value: {
      type: String
    },
    type: {
      type: String,
      default: 'text'
    }
  },
  methods: {
    handleInput (evt) {
      this.$emit('input', evt.target.value)
      const findParent = parent => {
        while (parent) {
          if (parent.$options.name === 'LgFormItem') {
            break
          } else {
            parent = parent.$parent
          }
        }
        return parent
      }
      const parent = findParent(this.$parent)
      if (parent) {
        parent.$emit('validate')
      }
    }
  }
}
</script>

<style>

</style>

3. Validation problem: install async validator and call the validation rules in rules through validator

// FORMiTEM.VUE
<template>
  <div>
    <label>{{ label }}</label>
    <div>
      <slot></slot>
      <p v-if="errMessage">{{ errMessage }}</p>
    </div>
  </div>
</template>

<script>
import AsyncValidator from 'async-validator'
export default {
  name: 'LgFormItem',
  inject: ['form'],
  props: {
    label: {
      type: String
    },
    prop: {
      type: String
    }
  },
  mounted() {
    this.$on('validate', () => {
      this.validate()
    })
  },
  data () {
    return {
      errMessage: ''
    }
  },
  methods: {
    validate () {
      if (!this.prop) return
      const value = this.form.model[this.prop]
      const rules = this.form.rules[this.prop]

      const descriptor = { [this.prop]: rules }
      const validator = new AsyncValidator(descriptor)
      return validator.validate({ [this.prop]: value }, errors => {
        if (errors) {
          this.errMessage = errors[0].message
        } else {
          this.errMessage = ''
        }
      })
    }  
  }
}
</script>

<style>

</style>

4. Add methods to stForm. By default, stFormItem is the first level sub component of stForm. Use this$ Children get all the sub components and find the sub level with prop, then traverse and call the validate method of the sub component

...
methods: {
  login () {
    console.log('button')
    this.$refs.form.validate(valid => {
      if (valid) {
        alert('Verification successful')
      } else {
        alert('Validation failed')
        return false
      }
    })
  }
}
...
// FORM.VUE
<template>
  <form>
    <slot></slot>
  </form>
</template>

<script>
export default {
  name: 'LgForm',
  provide () {
    return {
      form: this
    }
  },
  props: {
    model: {
      type: Object
    },
    rules: {
      type: Object
    }
  },
  methods: {
    validate (cb) {
      const tasks = this.$children
        .filter(child => child.prop)
        .map(child => child.validate())

      Promise.all(tasks)
        .then(() => cb(true))
        .catch(() => cb(false))
    }
  }
}
</script>

<style>

</style>

7, Monorepo

It is mainly used to manage components
Two types of project organization
Multirepo -- each package corresponds to a project (multiple repositories)
Monorepo - one project warehouse manages multiple modules / packages
Generally, in the project, each module is placed in the packages folder, and each module has an independent folder. The directory structure is as follows

Including index JS is an entry file, which can be used as Vue Use to import

import Button from './src/button.vue'
// Vue can be used directly by others Use to register the plug-in
Button.install = Vue => {
  // Register a global component and write the name of the component
  Vue.component(Button.name, Button)
}
export default Button

8, Storybook

Visual component display platform

  • Display components interactively in an isolated development environment
  • Develop components independently
  • Support a variety of frameworks, such as Vue, React, Angular, etc
    Installation method:
  1. npx -p @storybook/cli sb init --type vue
  2. yarn add vue
  3. yarn add vue-loader vue-template-compiler --dev

    Start yarn storybook
    Package and generate a static website, yarn build storybook
module.exports = {
  "stories": [
    "../stories/**/*.stories.mdx", // **Match folders at any level 
    "../stories/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials"
  ]
}

.storybook/main.js is equivalent to the configuration file of storybook, which sets the path of stories
storybook is a collection of stories, which is used to create the content to be presented on the interface
addons is a plug-in

use:
Add file to path * * stories.js
.stories/main.js to modify the path of the stories file

 module.exports = {
 // In the future, all stories will be stored in stories.js at the end of the file
  stories: ['../packages/**/*.stories.js'],
  addons: ['@storybook/addon-actions', '@storybook/addon-links'],
};

yarn storybook running

cd packages
cd formitem
yarn add async-validator

packages/form/stories/form.stories.js

import LgForm from '../'
import LgFormItem from '../../formitem'
import LgInput from '../../input'
import LgButton from '../../button'

export default {
  title: 'LgForm',
  component: LgForm
}

export const Login = () => ({
  components: { LgForm, LgFormItem, LgInput, LgButton },
  template: `
    <lg-form class="form" ref="form" :model="user" :rules="rules">
      <lg-form-item label="user name" prop="username">
        <!-- <lg-input v-model="user.username"></lg-input> -->
        <lg-input :value="user.username" @input="user.username=$event" placeholder="enter one user name"></lg-input>
      </lg-form-item>
      <lg-form-item label="password" prop="password">
        <lg-input type="password" v-model="user.password"></lg-input>
      </lg-form-item>
      <lg-form-item>
        <lg-button type="primary" @click="login">Sign in</lg-button>
      </lg-form-item>
    </lg-form>
  `,
  data () {
    return {
      user: {
        username: '',
        password: ''
      },
      rules: {
        username: [
          {
            required: true,
            message: 'enter one user name'
          }
        ],
        password: [
          {
            required: true,
            message: 'Please input a password'
          },
          {
            min: 6,
            max: 12,
            message: 'Please enter 6-12 Bit cipher'
          }
        ]
      }
    }
  },
  methods: {
    login () {
      console.log('button')
      this.$refs.form.validate(valid => {
        if (valid) {
          alert('Verification successful')
        } else {
          alert('Validation failed')
          return false
        }
      })
    }
  }
})

9, yarn workspaces

Project dependency

npm does not support workspaces

There are dependent plug-ins in multiple modules, which can be managed uniformly through yarn workspaces
Open the workspace of yarn:

  • Package. Of the project root directory JSON
    • "private": true, / / the root directory of the workspace is generally scaffolding and does not need to be published. Here to prevent accidental exposure of content
    • "workspaces": ["packages / *"] / / set the subdirectory of the workspace, and use * to specify any package in the packages directory

yarn workspaces use

  • Install development dependencies to the workspace root directory
    • yarn add jest -D -W jest is a unit testing tool developed by facebook
  • Dependent workspace assigned to installation
    • yarn workspace lg-button add loadsh@4 The package name here is our package Set in JSON
  • Install dependencies for all workspaces
    • yarn install

You can use yarn add * * * to install dependencies on all files, or you can use yarn workspace name add * * * to install dependencies on a single component named name, which may cause duplicate dependent components in each package. Therefore, you can add nodes in packages_ Modules are deleted, and then the dependencies required by multiple packages can be installed to the root directory through yarn install in the root directory, and the dependencies required by a single package can be installed to the current package

Monorepo's project structure generally cooperates with yarn workspaces to manage package dependencies. Package of vue3 and react JSON has enabled workspaces to facilitate dependency management

10, Lerna

lerna is a project that babel uses to maintain its Monorepo and open source

  • lerna is a workflow tool that optimizes the use of git and npm to manage multi package warehouses
  • Used to manage javascript projects with multiple packages
  • It can submit code to git and npm warehouses with one click

It can also be used to manage package dependencies. You can choose whether to use npm or yarn to manage dependencies. It needs to be configured separately. If yarn is used, you can also open yarn workspaces. Generally, lerna and yarn workspaces are used together

javascript projects used to manage multiple packages can submit code to git and npm warehouses at one click. Generally, workspaces are used to manage package dependencies and Lerna is used to publish packages
Installation and use
yarn add lerna -g
lerna init -- initialize and execute git init
If the current project is not managed by git, it will initialize GIT and create a lerna.exe in the root directory of the project The configuration file of JSON (recording the initialization version of the current project and the path of the package to be managed) is in package Add development dependencies to JSON
lerna publish -- log in to npm, publish to npm and transfer to git

npm whoami view the user name to log in to the npm website
npm config get registry view the current image source
yarn lerna execute lerna publish

11, Unit testing of Vue components

Unit test is to test the input and output of a function, and use the way of assertion to judge whether the actual output is the same as the predicted output according to the input
Benefits of component unit testing
reference resources: https://vue-test-utils.vuejs.org/zh/guides/#%E8%B5%B7%E6%AD%A5
Provide documentation describing component behavior
Save time for manual testing
Reduce bug s when developing new features
Improved design
Promote reconstruction
Installation dependency
Vue Test Utils vue official library of component unit tests
The jest unit test framework is easy to combine with vue and has the least configuration, but it does not support file components. Therefore, a preprocessor is needed to hand over the js code, which is the result of compiling the single file component of vue, to jest for processing. vue officially provides a preprocessor vue jest for jest
Vue jest supports most of the functions of the file component. esmodule syntax and syntax of some es new features will be used in the test file. At this time, the Babel jest plug-in is required to downgrade the test code
babel-jest

yarn add jest @vue/test-utils vue-jest babel-jest -D -W

to configure
Package. Under the root directory json

// package.json
"scripts": {
   "test": "jest"
  ...
}

Jest. In the root directory config. js

// jest.config.js
module.exports = {
  "testMatch": ["**/__tests__/**/*.[jt]s?(x)"],// Matching path
  "moduleFileExtensions": [
    "js",
    "json",
    // Tell Jest to handle ` * vue ` file
    "vue"
  ],
  "transform": {
    // Use 'vue jest' to process ` * vue ` file
    ".*\\.(vue)$": "vue-jest",
    // Handle js with 'Babel jest'
    ".*\\.(js)$": "babel-jest" 
  }
}

Babel. In the root directory config. js

module.exports = {
  presets: [
    [
      '@babel/preset-env'
    ]
  ]
}

Finally, when running, you will be prompted that babel cannot be found.
Reason: Vue test relies on babel6 and currently babel7
Babel bridge yarn add babel-core@bridge -D -W
-D is development dependency - W is installed in the root directory of the workspace. If it is not added, an error will be reported

Unit testing of Vue components

Basic API
JEST reference https://www.jestjs.cn/

// packages/input/__tests__/input.test.js
import input from '../src/input.vue'
import { mount } from '@vue/test-utils' // The API mounting component provided in it is required 
// Jest does not need to import because the test file is loaded and executed by jest
// describe creates a code block and puts the relevant tests of input into this code block
// Does the test include
describe('lg-input', () => {
  test('input-text', () => {
    const wrapper = mount(input) // Mount components
    // Get the html tag generated by this component and judge whether it contains
    expect(wrapper.html()).toContain('input type="text"')
  })
  // Set the type to password and test at the same time
  test('input-password', () => {
    const wrapper = mount(input, {
      propsData: { // Set props
        type: 'password'
      }
    })
    expect(wrapper.html()).toContain('input type="password"')
  })
// Test whether value is admin
  test('input-password', () => {
    const wrapper = mount(input, {
      propsData: { 
        type: 'password',
        value: 'admin'
      }
    })
    // wrapper.props this method can obtain the props object of the generated component. If value is passed, it obtains the value of the value attribute of the props of the generated component
    expect(wrapper.props('value')).toBe('admin')
  })
// Snapshot is a method provided in jest
// First run: save the text content to a specific file in the same directory as the current file/__ snapshots__
// The second run: compared with the first run, the same is successful
// yarn test -u command to delete and regenerate the snapshot
  test('input-snapshot', () => {
    const wrapper = mount(input, {
      propsData: {
        type: 'text',
        value: 'admin'
      }
    })
    // Take a snapshot of the dom object corresponding to the mounted component, and put it into a specific text file to record the first call
    expect(wrapper.vm.$el).toMatchSnapshot()
  })
})

12, Rollup

Rollup is a module packer
Rollup supports tree shaking
The result of packaging is smaller than that of Webpack
Rollup is more appropriate when developing framework / component libraries

pack

Installation dependency
Rollup
Rollup plugin tester compression
rollup-plugin-vue@5.1.9 Compile vue2 single file component into js
vue-template-compiler
Create rollup in the input directory config. js

import { terser } from 'rollup-plugin-terser'
import vue from 'rollup-plugin-vue'
module.exports = [
  {
    input: 'index.js',
    output: [
      {
        file: 'dist/index.js',
        format: 'es'//The packaging mode of the configuration module is es for es6 and cjs for Commonjs
      }
    ],
    plugins: [
      vue({
        css: true, //Insert the style in the single file component into the style tag in html
        compileTemplate: true // Convert component into render function
      }),
      terser() // Compress code  
    ]
  }
]

Find the package in the input package scripts configuration of JSON
When you enter the rollup command, you load rollup by default config. JS this file

"build": "rollup -c"

Run package / single package

yarn workspace lg-input run build 

Pack multiple packages

In addition to the above several dependencies
yarn add @rollup/plugin-json rollup-plugin-postcss @rollup/plugin-node-resolve -D -W

@Rollup / plugin json: enables rollup to load json files as modules, which will be used in configuration files
rollup-plugin-postcss :
@Rollup / plugin node resolve: package the dependent third-party packages in the packaging process

Create rollup from the project root directory config. The function of this package configuration file is to generate the next package JSP for all packages

import fs from 'fs'
import path from 'path'
import json from '@rollup/plugin-json'
import vue from 'rollup-plugin-vue'
import postcss from 'rollup-plugin-postcss'
import { terser } from 'rollup-plugin-terser'
import { nodeResolve } from '@rollup/plugin-node-resolve'

const isDev = process.env.NODE_ENV !== 'production'

// Public plug-in configuration
const plugins = [
  vue({
    // Dynamically inject css as a <style> tag
    css: true,
    // Explicitly convert template to render function
    compileTemplate: true
  }),
  json(),
  nodeResolve(),
  postcss({
    // Insert css into style
    // inject: true,
    // Put css in the same directory as js
    extract: true
  })
]

// If it is a development environment, add the compression plug-in to the plug-in array to start compression
isDev || plugins.push(terser())

// packages folder path. As the following path of processing
const root = path.resolve(__dirname, 'packages')

module.exports = fs.readdirSync(root) // Read everything in the directory
  // Filter, keep only folders
  .filter(item => fs.statSync(path.resolve(root, item)).isDirectory()) // And filter out the directory, that is, the folder of the package to be processed
  // Create a corresponding configuration for each folder
  .map(item => {
    const pkg = require(path.resolve(root, item, 'package.json')) // Find the package. In each directory JSON file
    return { // Configuration of rollup corresponding to each package
      input: path.resolve(root, item, 'index.js'),
      output: [
        {
          exports: 'auto',
          file: path.resolve(root, item, pkg.main),
          format: 'cjs'
        },
        {
          exports: 'auto',
          file: path.join(root, item, pkg.module),
          format: 'es'
        },
      ],
      plugins: plugins // Configure the plug-in. When the map ends, it returns an array, and each object in the array represents the configuration of a package
    }
  })

Set package. In each package main and module fields in JSON

"main": "dist/cjs/index.js",
"module": "dist/es/index.js"

Package. Of the root directory Configuring scripts in JSON

"build": "rollup -c" **

13, Setting environment variables

Delete unused content in the item
stories is generated when the storybook is initialized
Storybook static is generated when storybook is packaged

  • yarn add cross-env -D -W
  • Package. Of the root directory The scripts build command in JSON is modified to
    "build:prod": "cross-env NODE_ENV=production rollup -c",
    "build:dev": "cross-env NODE_ENV=development rollup -c"
  • When running, you will find that the code is compressed in build:prod mode

14, Clean up

remove directory specified
yarn add rimraf -D -W
Each package is configured

"scripts": {
    "del": "rimraf dist"
}
yarn workspaces run del

Clear all packages node_modules
 Configuration under root directory
"scripts": {
    "clean": "lerna clean", 
  }

15, Basic structure of component generation based on template

yarn add plop -W -D

16, Release

yarn build:prod
npm whoami
yarn lerna

Keywords: Vue.js

Added by SimpleManWeb on Sat, 19 Feb 2022 08:36:48 +0200