Load the DEMO warehouse address as required

background

Our front-end team has a UI component library supporting the company’s business system. After several iterations, the component library is very large.

Component library dependencies are managed on NPM. The component library is exported from the index.js root directory of the project. All components in the project are imported into the file and component installation methods are provided.

index.js

import Button from "./button";
import Table from "./table";
import MusicPlayer from "./musicPlayer";

import utils from ".. /utils"
import * as directive from ".. /directive";
import * as filters from ".. /filters";

const components = {
    Button,
    Table,
    MusicPlayer
}

const install = (Vue) = > {
    Object.keys(components).forEach(component= > Vue.use(component));
    
    // Continue to mount some services here
}

if (typeof window! = ='undefined' && window.Vue) {
  install(Vue, true);
}

export default{ install, ... components }Copy the code

The component library does not export the compiled dependency files. When used by business systems, components can be registered by installing and importing dependencies.

import JRUI from 'jr-ui';
Vue.use(JRUI);
Copy the code

Compilation of component libraries is by-passed to the compilation service of the business system.

That is, component library projects are not compiled themselves and are exported as components. Node_module is like a free cloud disk for storing component library code.

Because compiled by the business system, in the business system. Component library code can be debugged as directly as local files. It’s pretty straightforward and doesn’t require any extra configuration that relies on exports.

But there are drawbacks

  1. More specialized code cannot be used in the component library

    Vue-cli statically compiles.vue files referenced in node_module, but does not compile other files in node_module once the component library code has special syntax extensions (JSX), or special languages (TypeScript). At this point, the project will fail to run.

  2. Special variables in the component library that use Webpack will not work

    The WebPack configuration in the component library is not executed by the business system, so properties such as path aliases in the component library cannot be used

  3. Component library dependencies are loaded in full each time

    Index.js itself is a full component import, so even if the business system uses only part of the components, index.js will package all the component files (image resources, dependencies) in the dependency volume is always full size.

There is no business system that only uses one or two components; every business system needs most of them.

Almost every project uses common basic components such as buttons, input fields, drop-down options, tables, and so on.

Only some components are used in a few specific lines of business, such as rich text editors and music players.

Component classification

In order to solve the above problems, and to complete the effect of introduction on demand. Two component export modes are available: full export and basic export.

Component exports are divided into two types. Base components, importing components as needed.

The evaluation criteria for imported components on demand are:

  1. Less use of business systems
  2. A component contains third-party dependencies that are large in size or resource files
  3. Not internally referenced by another component

Full export mode Exports all components. Basic export only basic components. If you need to import components on demand, import corresponding components.

Adjust to import on demand

Refer to the element-UI export scheme for component dependencies exported by the component library and provide separate packaged dependency files for each component.

To export the index.js file in full, a new file base.js is added to the directory of the same directory as index.js to export basic components.

base.js

import Button from "./Button";
import Table from "./table";

const components = {
    Button,
    Table
}

const install = (Vue) = > {
    Object.keys(components).forEach(component= > Vue.use(component));
}

export default{ install, ... components }Copy the code

Modified the component library scaffolding tool to add additional packaging configuration. Used to compile component files and output compiled dependencies.

vue.config.js

const devConfig = require('./build/config.dev');
const buildConfig = require('./build/config.build');

module.exports = process.env.NODE_ENV === 'development' ? devConfig : buildConfig;
Copy the code

config.build.js

const fs = require('fs');
const path = require('path');
const join = path.join;
// Get the target file based on the current path
const resolve = (dir) = > path.join(__dirname, '.. / ', dir);

/** * @desc uppercase dash * @param {*} STR */
function upperCasetoLine(str) {
  let temp = str.replace(/[A-Z]/g.function (match) {
    return "-" + match.toLowerCase();
  });
  if (temp.slice(0.1) = = =The '-') {
    temp = temp.slice(1);
  }
  return temp;
}

@param {String} path */
function getComponentEntries(path) {
    let files = fs.readdirSync(resolve(path));

    const componentEntries = files.reduce((fileObj, item) = > {
      // File path
      const itemPath = join(path, item);
      // In the folder
      const isDir = fs.statSync(itemPath).isDirectory();
      const [name, suffix] = item.split('. ');
    
      // The entry file in the file
      if (isDir) {
        fileObj[upperCasetoLine(item)] = resolve(join(itemPath, 'index.js'))}// The entry file outside the folder
      else if (suffix === "js") {
        fileObj[name] = resolve(`${itemPath}`);
      }
      return fileObj
    }, {});
    
    return componentEntries;
}

const buildConfig = {
  // Output file directory
  outputDir: resolve('lib'),
  / / webpack configuration
  configureWebpack: {
    // Import file
    entry: getComponentEntries('src/components'),
    // Output configuration
    output: {
      // File name
      filename: '[name]/index.js'.// Build the dependency type
      libraryTarget: 'umd'.// The exported item in the library
      libraryExport: 'default'.// The dependency name for the reference
      library: 'jr-ui',}},css: {
    sourceMap: true.extract: {
      filename: '[name]/style.css'}},chainWebpack: config= > {
    config.resolve.alias
      .set("@", resolve("src"))
      .set("@assets", resolve("src/assets"))
      .set("@images", resolve("src/assets/images"))
      .set("@themes", resolve("src/themes"))
      .set("@views", resolve("src/views"))
      .set("@utils", resolve("src/utils"))
      .set("@mixins", resolve("src/mixins"))
      .set("jr-ui", resolve("src/components/index.js")); }}module.exports = buildConfig;
Copy the code

The NPM run build command executes this webpack configuration.

In configuration, all entry files in the component directory are looked for. Each entry file is compiled according to the Settings and output to the specified path.

configureWebpack: {
    // Import file
    entry: getComponentEntries('src/components'),
    // Output configuration
    output: {
      // File name
      filename: '[name]/index.js'.// Output the dependency type
      libraryTarget: 'umd'.// The exported item in the library
      libraryExport: 'default'.// The dependency name for the reference
      library: 'jr-ui',}},css: {
    sourceMap: true.extract: {
      filename: '[name]/style.css'}}Copy the code
function getComponentEntries(path) {
    let files = fs.readdirSync(resolve(path));

    const componentEntries = files.reduce((fileObj, item) = > {
      // File path
      const itemPath = join(path, item);
      // In the folder
      const isDir = fs.statSync(itemPath).isDirectory();
      const [name, suffix] = item.split('. ');
    
      // The entry file in the file
      if (isDir) {
        fileObj[upperCasetoLine(item)] = resolve(join(itemPath, 'index.js'))}// The entry file outside the folder
      else if (suffix === "js") {
        fileObj[name] = resolve(`${itemPath}`);
      }
      return fileObj;
    }, {});
    
    return componentEntries;
}
Copy the code

The directory of components in the project is as follows, and the configuration will package and compile each component and export it to lib

Components component files directory │ │ - button │ │ - button. The vue button component │ └ ─ index. The js button component export file │ │ - input │ │ - input. The vue input component │ └ ─ ├ ─ ├ ─ sci-imp, ├ ─ sci-imp, ├ ─ sci-imp, sci-imp, sci-imp, sci-imp, sci-imp, sci-imp, sci-imp, sci-imp, sci-imp, sci-imp Basic components of the export file └ ─ index. The export of all components in the js file lib compiled file directory │ │ - button │ │ - style. The CSS style button component dependence │ └ ─ index. The js button components depend on the file │ │ - Input │ │ - style.css. CSS input component dependence style │ └ ─ index. The js input component dependencies │ │ - music - player │ │ - style.css. CSS musicPlayer components depend on the style │ └ ─ │ ├ ─ ├ ─ ├ ─ ├ ─ ├ ─ ├ ─ ├ ─ ├ ─ ├ ─ └ Index.js all component dependency filesCopy the code

UpperCasetoLine is handled by humping the entry names to get all the entries for the component, because babel-plugin-import is converted to a bar-separated path when introduced on demand, if the component name is humped.

For example, service system import

import { MusicPlayer } from "jr-ui"

/ / into
var MusicPlayer = require('jr-ui/lib/music-player');
require('jr-ui/lib/music-player/style.css');
Copy the code

Because of the component library naming convention, component folder names are not separated by stripes. But in order for babel-plugin-import to work correctly, the entry filename of each file is converted.

If the method does not convert the name, you can also disable the name conversion by setting camel2DashComponentName to false with the plugin-import configuration in babel.config.js.

Babel-plugin-import Path name issue

When business system is used

Full export All components are exported by default

// Full export
import JRUI from "jr-ui";
import "jr-ui/lib/index/index.css";

Vue.use(JRUI);
Copy the code

Basic export Only exports basic components. If additional components need to be used, install the babel-plugin-import plug-in and configure babel.config.js to complete the transformation of import statements

npm i babel-plugin-import -D
Copy the code

Business system — babel.config.js configuration

module.exports = {
  presets: ["@vue/app"["@babel/preset-env", { "modules": false }]],
  plugins: [["import",
      {
        "libraryName": "jr-ui"."style": (name) = > {
            return `${name}/style.css`; }}]]}Copy the code
// Base export
import JRUI_base from "jr-ui/lib/base";
import "jr-ui/lib/base/index.css";
Vue.use(JRUI_base);

// Use additional imported components as needed
import { MusicPlayer } from "jr-ui";
Vue.use(MusicPlayer);
Copy the code

Debug component library code in business system

If you still want to debug component library code, when importing a component, directly import the component export file under components within the component library dependencies and override the installation. You can debug the target component.

import button from "jr-ui/src/components/button";
Vue.use(button);
Copy the code

The optimization effect

In the case of large component libraries, the optimization effect is very obvious. It’s a Megabyte smaller when using the base components. It also reduces unnecessary third-party dependencies on file resources within many components.

Case warehouse address, if there are questions and mistakes, welcome to ask or point out.

Have a happy labor day holiday 🙂

Have a nice day.

The resources

Vue-cli Perform parsing

babel-plugin-import