In the development of front-end projects, often according to business requirements, precipitation of some project UI components/functional modules (hereinafter referred to as components) and so on; These components are initially maintained within the same project and reused by different pages or modules within the project as the component is refined, a growth phase that focuses solely on functionality and robustness.

With the development of the business, the original project may have to undergo fission, into several similar but have different projects, such as in the initial project experience, need to promote to the similar forms or customized according to the requirements of different clients, in this case it is often difficult to keep all items large version of the idealized or subsequent synchronization development progress, They can only develop separately. At this point, “reusable components” that seemed to be easy to use at the beginning often need to be maintained in separate projects, or unexpected problems arise and need to be redesigned.

Taking the front-end project of Vue technology stack as an example, this paper tries to explore a simple method of abstractly extracting reusable components across projects.

Common realities of reusable components

  • Reuse of components is limited to a single project
  • One development, n iterationswithsystem
  • The fission of the project multiplies the problem, with each revision/change being synchronized n times
  • Sibling projects may have similar but different dependency libraries, or versions that differ greatly
  • Component reuse can also be problematic depending on the unit test environment or version

For the different aspects of the same component in different projects, take the daterange. vue component of the elder-date-picker in element-UI as an example:

Where the project Base Component library Representative problems found
A [email protected]
  • The picker results show that the underlying DOM is implemented by a single input, which is different from the latest version of the component library
  • El-date-picker does not yet support the value-format attribute
B
C
D
E [email protected]
  • The INTERNAL component’s V-Model does not trigger the callback correctly and needs to be fixed with Watch
  • In each of these projects, additional styles need to be added
F

For a variety of reasons, several projects rely on similar but not identical UI libraries; And the project size is too large, the maintenance team is different, etc., all make the unified base component library πŸ” impossible ~ πŸ” you are too beautiful, this is very embarrassing ~

How to converge maintenance points?

  • For just a few of the projects in this example, there are six maintenance points, and the work is Γ—6
  • If converges to a unified library, then the maintenance point becomes two, and only needs to distinguish between the base version libraries
  • For most simple components, where versions of the base component library do not make a difference, or simple components that do not reference the Element-UI component library at all, the maintenance point can be reduced to one

What components are common?

  • Abstract enough to contain no business logic, or extensible enough
  • Try not to include$t,$routerDependencies related to the project environment
  • Have unit tests with sufficient coverage
  • The necessary documentation is available, or the functionality is fully described through unit tests
  • It is best to provide working examples as well

Published to the NPM

Within a specific project, a component simply needs to reference its source code;

For a common component library across projects, one approach is to maintain a submodule (git subtree or Submodule) within each project that points to the source code of the component library, but this approach is cumbersome to maintain and is not commonly used.

Another approach we are more comfortable with is to refer directly to the component’s registered name (name in package.json) after installation via NPM.

Of course, if your own components are more or less business logic and less common to external projects, and your company maintains a mirror of NPM, you can choose to publish them to this internal environment.

The main steps for publishing an NPM component are:

Sign up at npmjs.com, or from the command line:

npm adduser
Copy the code

Confirm login before Posting:

npm login
Copy the code

Change package.json manually before publishing, or update the project version number using the command line. Note that the release number cannot be the same each time:

npm version x.x.x
Copy the code

Performing a release:

npm publish
Copy the code

Directly from the command line open the project home page to view:

npm home [name]
Copy the code

See the full documentation for more commands: docs.npmjs.com/cli-documen…

Also note that properly configuring the Repository field in package.json displays a link to the repository on the component’s NPM home page.

Package components with rollup instead of Webpack

In this example, rollup is selected as the packaging tool:

  • Webpack is powerful, but the configuration is complex and the generated code is redundant
  • Rollup is more suitable for compiling library, component, and other source code
  • Rollup is based on plug-in extension packaging and is relatively simple to configure
  • Rollup’s configuration items are highly similar to WebPack for easy migration and adaptation

A basic set of configurations

Assume that the component library structure is planned as follows:

β”œβ”€.babelrc β”œβ”€.Eslintignore β”œβ”€.Eslintrc.js β”œβ”€CHANGELOG.md β”œβ”€ Package.json β”œβ”€.md β”œ ─ postcss. Config. Js β”œ ─ a rollup. Config. Js β”œ ─ dist / β”œ ─ example / β”œ ─ node_modules / β”œ ─ SRC / β”œ ─ __mocks__ / β”” ─ __tests__ /Copy the code

The minimum NPM scripts are as follows:

// package.json

"scripts": {
  "build": "rollup --config"
},
Copy the code

The more basic rollup configuration is as follows:

// rollup.config.js

import path from 'path';
import json from 'rollup-plugin-json';
import { uglify } from 'rollup-plugin-uglify';
import alias from 'rollup-plugin-alias';
import vue from 'rollup-plugin-vue';
import babel from 'rollup-plugin-babel';
import commonjs from 'rollup-plugin-commonjs';
import resolve from 'rollup-plugin-node-resolve';
import nodeGlobals from 'rollup-plugin-node-globals';
import bundleSize from 'rollup-plugin-filesize';
import { eslint } from 'rollup-plugin-eslint';
import pkg from './package.json';

const pathResolve = p= > path.resolve(__dirname, p);

const extensions = ['.js'.'.vue'];

module.exports = {
  input: 'src/index.js'.output: {
    file: 'dist/bundle.min.js'.format: 'umd'.name: 'MyComponents'.globals: {
      vue: 'Vue'.echarts: 'echarts'.lodash: 'lodash'
    },
    sourcemap: true
  },
  external: Object.keys(pkg.dependencies),
  plugins: [
    resolve({
      extensions,
      browser: true
    }),
    eslint({
      extensions,
      exclude: ['**/*.json'].cache: true.throwOnError: true
    }),
    bundleSize(),
    commonjs(),
    nodeGlobals(),
    vue({
      template: {
        isProduction: !process.env.ROLLUP_WATCH,
        compilerOptions: { preserveWhitespace: false}},css: true
    }),
    babel({
      exclude: 'node_modules/**'
    }),
    alias({
      The '@': pathResolve('src')
    }),
    json(),
    uglify()
  ]
};
Copy the code

The following is a brief description of the configuration:

  • The order of the plug-ins in the above example is important
  • The Node-globals plug-in injects variables such as process into the packaged file
  • The esLint plugin checks the syntax before packaging and can basically reuse the.eslintrc.js configuration file from your normal project
  • The bundleSize plugin is used to display the volume of the target file after packaging
  • The CSS field in the Vue plug-in that indicates whether the embedded style is packaged into the target JS
  • Continue compiling ES6 code with Babel, rather than the lighter Buble that is also often used with rollup, for reuse with JEST
  • Json components address situations where json files may be imported directly from source code
  • External configuration means that dependencies contained in package.json dependencies are not packaged into the component, but need to be installed in the specific project

Related syntax conversion and syntax checking configurations:

// .babelrc

{
  "presets": [["env", { "modules": false }]],
  "env": {
    "test": {
      "presets": [["env", { "targets": { "node": "current"}}]]}}}Copy the code
// .eslintignore

__tests__/*
*.css
Copy the code
// .eslintrc.js

module.exports = {
  extends: [
    "airbnb-base".'plugin:vue/essential'].rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off'.'no-debugger': 'error'.'space-before-function-paren': 'off'.'no-underscore-dangle': 'off'.'no-param-reassign': 'off'.'func-names': 'off'.'no-bitwise': 'off'.'prefer-rest-params': 'off'.'no-trailing-spaces': 'off'.'comma-dangle': 'off'.'quote-props': 'off'.'consistent-return': 'off'.'no-plusplus': 'off'.'prefer-spread': 'warn'.semi: 'warn'.indent: 'warn'.'no-tabs': 'warn'.'no-unused-vars': 'warn'.quotes: 'warn'.'no-void': 'off'.'no-nested-ternary': 'off'.'import/no-unresolved': 'off'.'no-return-assign': 'warn'.'linebreak-style': 'off'.'prefer-destructuring': 'off'.'no-restricted-syntax': 'warn'
  },
  parserOptions: {
    parser: 'babel-eslint'}}Copy the code

Configure the unit test environment

Maintenance point converges to a library, it should be noted that the corresponding risk is also highly concentrated, it can be said that all losses are both prosperous 🐢.

So unit testing is increasingly important. For component or module libraries that are conditional (such as THE DIRECTIVES of Vue are not that good for unit testing, but the filters pure functions are easy), buy one get one free should be unconditionally and freely used by developers of various projects. With good unit tests.

The following uses JEST as an example to list its main configurations:

// jest.config.js

module.exports = {
  modulePaths: [
    '<rootDir>/src/'].moduleFileExtensions: [
    'js'.'json'.'vue'].transform: {
    '^.+\\.vue$': 'vue-jest'.'^.+\\.jsx? $': 'babel-jest'
  },
  transformIgnorePatterns: [
    '/node_modules/'].moduleNameMapper: {
    '^ @ / (. *) $': '<rootDir>/src/$1'.'\\.(css|less|scss)$': '<rootDir>/__mocks__/emptyMock.js'
  },
  snapshotSerializers: [
    'jest-serializer-vue'].collectCoverage: true.collectCoverageFrom: [
    '<rootDir>/src/**/*.{js,vue}'.'! **/node_modules/**'].coveragePathIgnorePatterns: [
    '<rootDir>/__tests__/'].coverageReporters: [
    'text']};Copy the code

Where emptymock.js is used to ignore references to styles in use cases:

// __mocks__/emptyMock.js

module.exports = {};
Copy the code

Corresponding NPM scripts:

"scripts": {
  // ...
  "test": "jest"
},
"pre-commit": [
  "test"
],
Copy the code

The pre-commit package implements the hook function of unit testing before committing.

More on Vue unit testing is availableThis article.

Preview the components in action

Although static syntax has been checked and unit tests have passed, seeing is believing. It is also intuitive for other developers. With rollup-plugin-serve, you can run a browser environment with minimal configuration and see how the components actually perform.

Set the environment parameters in NPM Scripts to start the Demo page service for fully generic components and components suitable for a specific type of project:

"scripts": {
  // ...
  "dev:common": "rollup --watch --config rollup.config.dev.js --environment PROJ_ENV:common",
  "dev:A": "rollup --watch --config rollup.config.dev.js --environment PROJ_ENV:A",
  "dev:B": "rollup --watch --config rollup.config.dev.js --environment PROJ_ENV:B",
},
Copy the code

Add separate rollup.config.dev.js and start the service according to the environment parameters:

import serve from 'rollup-plugin-serve';
import postcss from 'rollup-plugin-postcss';
import baseConfig, {
  pathResolve,
  browserGlobals
} from './rollup.config.base'; . const PORT =3001;
const PROJECT = process.env.PROJ_ENV;

export default {
  input: pathResolve(`example/${PROJECT}/main.js`),
  output: {
    file: pathResolve(`example/${PROJECT}/dist/example.bundle.${PROJECT}.js`),
    format: 'umd'.name: 'exampleApp'.globals: browserGlobals,
    sourcemap: false
  },
  plugins: [
    postcss(),
    ...baseConfig.plugins,
    serve({
      port: PORT,
      contentBase: [
        pathResolve(`example/${PROJECT}`)]})]};Copy the code

Here, we assume that components are simply referred to in app. vue, regardless of routing, and the structure of the corresponding example directory might be as follows:

+---A | | App.vue | | index.html | | main.js | | | \---dist | example.bundle.A.js | +---B | | App.vue | | index.html | |  foo.css | | main.js | | | \---dist | example.bundle.B.js | \---common | App.vue | index.html | main.js | +---dist | example.bundle.common.js | \---fontsCopy the code

conclusion

When maintaining several homogeneous front-end projects at the same time, it is inevitable that some of the more common UI components/functional modules will be involved. Putting them together and publishing them on NPM, with sound unit tests, working demos, and necessary documentation, will greatly reduce the workload of maintaining the components.






–End–






Search fewelife concern public number reprint please indicate the source