Component library construction process

Posted by activomate on Tue, 14 Dec 2021 04:23:16 +0100

Recently, a vue component library has been created within the project, hoping to unify the logic and style of components in the project in the form of component library, so as to make the code more reusable.

This article mainly combs the whole structure and construction process of component library.

structure

First, let's introduce the code structure of the component library. The above is the overall code directory structure. The functions of each directory are as follows:

  1. packages: location of component source code, and each component is a subdirectory; packages / index. Is also provided JS as the entry of global components (details will be described later)
  2. lib: store compiled code
  3. build: related to construction tools (which will be highlighted later in the construction process)
  4. config: environment configuration related
  5. examples: doc documents
  6. Test: unit test code
  7. Others: eslint, babel, git related configuration files

Here, let's talk about packages in detail. First, let's take a look at the directory structure of packages:

The name of the subdirectory of packages is the name of the component, and each component will have an index Vue and index Sass serves as a component entry and a style entry. At the same time, in the root directory of packages, index JS, as the global registration component entrance, introduces all components and then calls Vue.. Component is registered as a global component.

ok, the directory structure is clear, but it is only a part of the development process. As for the final output content, it needs to be compiled based on specific use scenarios. The following are the use methods and specific compilation methods supported by the current component library.

Browser introduction

The browser must include script and link, so correspondingly, we need to package js and css files that contain all components. For the use scenario of script, you need to package all the code into one file libraryTarget: 'window' Mode, we can meet our requirements. Reconnection ExtractTextPlugin , you can get all the css content. The webpack entry file is packages / index JS, the final compiled file, is the entire file pirate JS and style file pirate css.

The configuration of webpack is as follows:

"use strict";
const path = require("path");
const webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");

function resolve(dir) {
  return path.join(__dirname, "..", dir);
}

module.exports = {
  entry: resolve("packages/index.js"),
  externals: {
    vue: {
      root: "Vue",
      commonjs: "vue",
      commonjs2: "vue",
      amd: "vue"
    }
  },
  output: {
    path: resolve("lib"),
    filename: "pirate.js",
    library: "pirate",
    libraryTarget: "window",
  },
  resolve: {
    extensions: [".js", ".vue"]
  },
  module: {
    rules: [{
        test: /\.vue$/,
        loader: "vue-loader",
        options: {
          js: {
            loader: "babel-loader",
            options: {}
          },
          scss: {
            loader: ["css-loader", "scss-loader"]
          },
          extractCSS: true
        }
      },
      {
        test: /\.js$/,
        loader: "babel-loader",
        include: [resolve("package")]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: "url-loader",
        options: {
          limit: 10000
        }
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin("pirate.css"),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ]
};

Load on demand

As for on-demand loading, the default method is, of course, to load js directly through import Xxx from "pirate/lib/xxx/index.js", and also through @ import ~ pirate / lib / index CSS manually loads styles.

But it is suggested to cooperate here babel-plugin-import Using this plug-in, the code will be more concise and comfortable. According to the requirements of Babel plugin import, index CSS will be generated in the lib/xxx/style directory. In this way, one line of code is required for on-demand loading: import {XXX} from 'pirate'.

Back to the compilation stage, you will naturally think of compiling with webpack. Each component is an entry, and then compile with webpack multi entry mode.

First, an automated collection of component directories is performed to generate components JSON is used as the webpack entry to implement the build / component JS code is as follows:

const fs = require('fs-extra');
const path = require('path');

function isDir(dir) {
  return fs.lstatSync(dir).isDirectory();
}

const json = {};
const dir = path.join(__dirname, '../packages');
const files = fs.readdirSync(dir);
files.forEach(file => {
    const absolutePath = path.join(dir, file);
    if (isDir(absolutePath)) {
        json[file] = `./packages/${file}`;
    }
});

console.log(JSON.stringify(json));

Then through node build / components js > components.json generates components JSON, which can be compiled through webpack. The webpack code is as follows:

'use strict'
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const components = require('../components.json');

function resolve(dir) {
  return path.join(__dirname, '..', dir)
}

module.exports = {
  entry: components,
  externals: {
    vue: {
      root: 'Vue',
      commonjs: 'vue',
      commonjs2: 'vue',
      amd: 'vue'
    },
  },
  output: {
    path: resolve('lib'),
    filename: '[name]/index.js',
    library: 'pirate',
    libraryTarget: 'umd',
    umdNamedDefine: true,
  },
  resolve: {
    extensions: ['.js', '.vue'],
  },
  module: {
    rules: [{
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          js: {
            loader: 'babel-loader',
            options: {},
          },
          scss: {
            loader: ['css-loader', 'scss-loader'],
          },
          extractCSS: true,
        }
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('package')],
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
        }
      },
    ]
  },
  plugins: [
    new ExtractTextPlugin('[name]/style/index.css'),
  ],
}

However, there is a problem, or an optimization point, that is, the entry code generated through webpack will package a layer of webpack launcher (for more information, please see my previous article) webpack modular principle commonjs,webpack modularization principle - ES module,webpack modular principle - Code Splitting )Generally speaking, as for on-demand loading, users will have their own webpack, so what the component library needs to do is compile vue files into js, that's all (even vue files are OK, but js is more appropriate considering more general scenarios).

Therefore, you can use the vue official vue-template-compiler , his job is to compile vue templates into independent vue objects. Here I will use the software developed by my colleagues vue-sfc-compiler Vue SFC compiler encapsulates Vue template compiler at the bottom and babel support at the top. It will be more convenient to use, but the purpose is the same.

Then, based on the files compiled by webpack above, I will overwrite them with smaller files compiled by Vue SFC compiler. The specific code is as follows:

const fs = require('fs-extra');
const compiler = require('vue-sfc-compiler');
const path = require('path');

function isDir(dir) {
    return fs.lstatSync(dir).isDirectory();
}

function compile(dir) {
    const files = fs.readdirSync(dir);
    files.forEach(file => {
        const absolutePath = path.join(dir, file);
        if (isDir(absolutePath)) {
            return compile(absolutePath);
        }
        if (/\.vue|.js$/.test(file)) {
            const source = fs.readFileSync(absolutePath, 'utf-8');
            const content = compiler(source).js;
            const outputPath = absolutePath.replace('packages', 'lib').replace('.vue', '.js');
            fs.outputFileSync(outputPath, content);
        }
    });
}

const dir = path.join(__dirname, '../packages');
compile(dir);

Global component registration

For the global component registration method, I will take this entry as the entry of the whole module, that is, the default use method.

In the last step, in the on-demand loading phase, each component has been compiled. In fact, the import file can only be converted with babel. gulp is used here. The code is as follows:

const gulp = require('gulp');
const babel = require('gulp-babel');
const path = require('path');

gulp.task('default', () =>
  gulp.src(path.join(__dirname, '../packages/index.js'))
  .pipe(babel({
    presets: ['env']
  }))
  .pipe(gulp.dest(path.join(__dirname, '../lib')))
);

summary

Finally, there is another regret. At present, the documentation has not been completed. Here, first describe the current idea, and then write a share after implementation.

Now the plan is to add a demo to each component directory Vue and doc md,demo.vue is used to demonstrate the current component functions, Doc MD as document content. Then, through an automated tool, all component demo s and doc s are combined to generate an html.