From scratch, build a development environment for Vue plug-ins and open source the plug-ins to NPM

Time:2021-11-23

Demo

Vue component – contains eslint and jest
Vue instruction

Build development environment

Generally speaking, when we want to write a Vue component or plug-in, we will choose to use webpack to build our own development environment instead of Vue cli to produce the development environment.

Initialize project

Use the command firstgit initInitialize a git repository to manage our code, and then use the commandnpm init -yTo quickly generate a package.json file (which will be modified as needed later). As follows:
From scratch, build a development environment for Vue plug-ins and open source the plug-ins to NPM

Configure the underlying webpack

Our goal is to write a Vue component or instruction, so webpack is certainly indispensable. We will use webpack to build our development environment. use firstnpm i -D webpack webpack-cliinstallwebpackandwebpack-cli(after webpack 4, it is required to install webpack CLI) into our project, and then create one in the root directory of the projectwebpack.config.jsThe file and name aresrcFolder for. The directory structure is as follows:
From scratch, build a development environment for Vue plug-ins and open source the plug-ins to NPM
./src/main.jsThe file will be our packaged entry file.webpack.config.jsFile is the file that we configure webpack for packaging. At this time, you canwebpack.config.jsWrite the basic configuration in.

const path = require('path');

const resolve = (p) => {
    return path.resolve(__dirname, p);
}

module.exports = {
    entry: {
        main: resolve('./src/main')
    },
    output: {
        filename: '[name].js',
        path: resolve('./dist')
    },
    mode: 'production'
};

For children’s shoes configured with webpack, we know that the above configuration obviously can not meet our development needs. We will adopt it in the developmentES6Syntax, then you need to introduce@babel/core @babel/preset-env babel-loader; If you need to parse the code of Vue, you need to introducevue-loader; The template file of Vue needs to be parsed and importedvue-template-compiler; useSCSSPrecompiled to write CSS code, you need tonode-sass sass-loader style-loader css-loader; To automatically prefix CSS, you need to introducepostcss-loader autoprefixer; To compile image and font files, you need to importfile-loader; usemini-css-extract-pluginExtract the CSS file in the project into a separate file; usehtml-webpack-pluginGenerate HTML file template;
After importing the above files, you can write out such configuration files:

// webpack.config.js
const path = require('path');
const vueLoaderPlugin = require('vue-loader/lib/plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const resolve = (p) => {
    return path.resolve(__dirname, p);
}

module.exports = {
    entry: {
        main: resolve('./src/main')
    },
    
    output: {
        filename: '[name].js',
        path: resolve('./dist')
    },

    resolve: {
        extensions: ['.vue', '.js', '.jsx', '.json'],
        alias: {
            '@': resolve('./src/')
        }
    },

    module: {
        rules: [
            {
                test: /\.vue$/,
                use: 'vue-loader'
            },
            {
                test: /\.jsx?$/,
                use: ['babel-loader', 'astroturf/loader'],
                exclude: /node_modules/
            },
            {
                test: /\.(css|scss|sass)$/,
                use: [{
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        hmr: true,
                        reloadAll: true
                    }
                }, 'css-loader', 'postcss-loader', 'sass-loader']
            },
            {
                test: /\.(eot|svg|ttf|woff|woff2)$/,
                use: ['file-loader']
            }
        ]
    },

    plugins: [
        new htmlWebpackPlugin({
            filename: 'index.html',
            inject: 'body'
        }),
        new vueLoaderPlugin(),
        new MiniCssExtractPlugin({
            filename: 'css/[name].[hash:8].css',
            chunkFilename: 'css/[id].[hash:8].css'
        })
    ]
};

When configuring Babel, we usually create a. Babelrc file under the root directory to configure Babel related properties separately

// .babelrc
{
    "presets": ["@babel/preset-env"]
}

When configuring the properties related to postcss, a file of postcss.config.js will be created

module.exports = {
  plugins: [require("autoprefixer")]
};

The above configuration is basically a relatively complete webpack configuration, which can be used again./src/main.jsWrite some ES6 code to test.
However, the above configuration will also cause some problems. If we write all the configurations together, in the production environment, we usually need to debug the source code and start a local server to run our code. In the production environment, the code needs to be compressed and there is no need for a local server. Therefore, in general, the configuration of development and production will be separated.

Separate the configuration of development and production environments

We will write the public configuration in a file, and then use it in the production configuration file and development configuration filewebpack-mergeTo merge configurations.npm i -D webpack-mergeInstall webpack merge.
Let’s adjust the project structure first.

--Example / * development environment directory for testing*/
    |-- App.vue
    |-- index.html
    |--Main.js / * entry file*/

--Src / * directory of components or plug-ins to be published*/
    |--Main.js / * entry file*/

--. babelrc / * Babel configuration file*/
-- .gitignore
-- package.json
--Postcss.config.js / * postcss configuration file*/
-- README.md
--Webpack.common.conf.js / * public webpack configuration file*/
--Webpack.dev.conf.js / * webpack configuration file of development environment*/
--Webpack.prod.conf.js / * webpack configuration file for production environment*/

Some codes are as follows:
Public configuration file: webpack.common.js

const path = require('path');
const vueLoaderPlugin = require('vue-loader/lib/plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const resolve = (p) => {
    return path.resolve(__dirname, p);
}

const isProd = process.env.NODE_ENV !== 'development';

module.exports = {
    output: {
        filename: '[name].js',
        path: resolve('./dist')
    },

    resolve: {
        extensions: ['.vue', '.js', '.jsx', '.json'],
        alias: {
            '@': resolve('./src/')
        }
    },

    module: {
        rules: [
            {
                test: /\.vue$/,
                use: 'vue-loader'
            },
            {
                test: /\.jsx?$/,
                use: ['babel-loader'],
                exclude: /node_modules/
            },
            {
                test: /\.(css|scss|sass)$/,
                use: [{
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        hmr: !isProd,
                        reloadAll: true
                    }
                }, 'css-loader', 'postcss-loader', 'sass-loader']
            },
            {
                test: /\.(eot|svg|ttf|woff|woff2)$/,
                use: ['file-loader']
            }
        ]
    },

    plugins: [
        new vueLoaderPlugin(),
        new MiniCssExtractPlugin({
            filename: 'css/[name].[hash:8].css',
            chunkFilename: 'css/[id].[hash:8].css'
        })
    ]
};

Development configuration file: webpack.dev.conf.js

const merge = require("webpack-merge");
const commonConfig = require("./webpack.common.conf");
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

const resolve = (p) => {
    return path.resolve(__dirname, p);
}

module.exports = merge(commonConfig, {
    entry: {
        main: resolve('./example/main')
    },

    mode: 'development',

    devServer: {
        contentBase: resolve('./dist'),
        port: 8090,
        host: '0.0.0.0',
        hot: true,
        hotOnly: false
    },

    devtool: '#cheap-module-source-map',

    plugins: [
        new htmlWebpackPlugin({
            template: resolve('./example/index.html'),
            filename: 'index.html',
            inject: 'body'
        })
    ]
});

Production environment configuration file: webpack.prod.conf.js

const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.conf');
const path = require('path');
const optimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const terserJsPlugin = require('terser-webpack-plugin');

const resolve = p => {
    return path.resolve(__dirname, p);
};

module.exports = merge(commonConfig, {
    entry: {
        main: resolve('./src/main')
    },

    output: {
        libraryTarget: 'commonjs2'
    },
    devtool: false /* 'source-map' */,

    optimization: {
        minimizer: [new optimizeCssAssetsWebpackPlugin(), new terserJsPlugin()]
    },

    mode: 'production'
});

The basic development environment is set up. Now you only need to modify itpackage.jsonFile, add the running script, and then write the corresponding code in the corresponding directory file and run it.

...
  "scripts": {
    "Dev": "webpack dev server -- color -- config. / webpack. Dev.conf", / * development environment*/
    "Build": "webpack -- config. / webpack. Prod.conf" / * production environment*/
  },
...

Full code reference:Vue instructions (Vue words highlight)

Introducing eslint and jest configurations

When developing open source components, writing unit tests can avoid many bugs. Jest can be introduced to test components, and eslint can also be introduced to standardize code.

eslint
Install eslint and related loaders and plug-ins:npm i -D eslint eslint-config-standard eslint-loader eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-vue, add a new one under the root directory.eslintrc.jsFile to configure eslint.

module.exports = {
    "env": {
        "browser": true,
        "es6": true
    },
    "extends": [
        "plugin:vue/essential",
        "standard"
    ],
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly"
    },
    "parserOptions": {
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "plugins": [
        "vue"
    ],
    "Rules": {/ * rules that override eslint*/
        "indent": ["warn", 4],
        "semi": 0,
        "no-undef": 0
    }
};

Modify the webpack.common.conf.js file

...
module: {
        rules: [
            {
                enforce: 'pre',
                test: /\.(vue|jsx?)$/,
                use: ['eslint-loader'],
                exclude: /node_modules/
            },
            ...
        ]
 }
 ...

jest
Install related plug-insnpm i -S babel-jest jest jest-serializer-vue jest-transform-stub jest-watch-typeahead vue-jest
newly addedjest.config.jsfile

// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html

module.exports = {
    // All imported modules in your tests should be mocked automatically
    // automock: false,

    // Stop running tests after `n` failures
    // bail: 0,

    // Respect "browser" field in package.json when resolving modules
    // browser: false,

    // The directory where Jest should store its cached dependency information
    // cacheDirectory: "C:\\Users\070\\AppData\\Local\\Temp\\jest",

    // Automatically clear mock calls and instances between every test
    // clearMocks: false,

    // Indicates whether the coverage information should be collected while executing the test
    // collectCoverage: false,

    // An array of glob patterns indicating a set of files for which coverage information should be collected
    collectCoverageFrom: ['**/*.{js,vue}', '!**/node_modules/**'],

    // The directory where Jest should output its coverage files
    coverageDirectory: 'coverage',

    // An array of regexp pattern strings used to skip coverage collection
    // coveragePathIgnorePatterns: [
    //   "\\\\node_modules\\\\"
    // ],

    // A list of reporter names that Jest uses when writing coverage reports
    coverageReporters: ['json', 'text', 'lcov', 'clover', 'html'],

    // An object that configures minimum threshold enforcement for coverage results
    // coverageThreshold: undefined,

    // A path to a custom dependency extractor
    // dependencyExtractor: undefined,

    // Make calling deprecated APIs throw helpful error messages
    // errorOnDeprecated: false,

    // Force coverage collection from ignored files using an array of glob patterns
    // forceCoverageMatch: [],

    // A path to a module which exports an async function that is triggered once before all test suites
    // globalSetup: undefined,

    // A path to a module which exports an async function that is triggered once after all test suites
    // globalTeardown: undefined,

    // A set of global variables that need to be available in all test environments
    // globals: {},

    // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
    // maxWorkers: "50%",

    // An array of directory names to be searched recursively up from the requiring module's location
    // moduleDirectories: [
    //   "node_modules"
    // ],

    // An array of file extensions your modules use
    moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node', 'vue'],

    // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
    moduleNameMapper: {
        '^@/(.*)$': '<rootDir>/src/$1',
        '^.+.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$':
            'jest-transform-stub'
    },

    // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
    modulePathIgnorePatterns: ['/node_modules/'],

    // Activates notifications for test results
    // notify: false,

    // An enum that specifies notification mode. Requires { notify: true }
    // notifyMode: "failure-change",

    // A preset that is used as a base for Jest's configuration
    // preset: undefined,

    // Run tests from one or more projects
    // projects: undefined,

    // Use this configuration option to add custom reporters to Jest
    // reporters: undefined,

    // Automatically reset mock state between every test
    // resetMocks: false,

    // Reset the module registry before running each individual test
    // resetModules: false,

    // A path to a custom resolver
    // resolver: undefined,

    // Automatically restore mock state between every test
    // restoreMocks: false,

    // The root directory that Jest should scan for tests and modules within
    // rootDir: undefined,

    // A list of paths to directories that Jest should use to search for files in
    // roots: [
    //   "<rootDir>"
    // ],

    // Allows you to use a custom runner instead of Jest's default test runner
    // runner: "jest-runner",

    // The paths to modules that run some code to configure or set up the testing environment before each test
    // setupFiles: [],

    // A list of paths to modules that run some code to configure or set up the testing framework before each test
    // setupFilesAfterEnv: [],

    // A list of paths to snapshot serializer modules Jest should use for snapshot testing
    snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],

    // The test environment that will be used for testing
    // testEnvironment: "jest-environment-jsdom",

    // Options that will be passed to the testEnvironment
    // testEnvironmentOptions: {},

    // Adds a location field to test results
    // testLocationInResults: false,

    // The glob patterns Jest uses to detect test files
    testMatch: [
        '**/__tests__/**/*.[jt]s?(x)'
    ],

    // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
    // testPathIgnorePatterns: [
    //   "\\\\node_modules\\\\"
    // ],

    // The regexp pattern or array of patterns that Jest uses to detect test files
    // testRegex: [],

    // This option allows the use of a custom results processor
    // testResultsProcessor: undefined,

    // This option allows use of a custom test runner
    // testRunner: "jasmine2",

    // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
    testURL: 'http://localhost',

    // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
    // timers: "real",

    // A map from regular expressions to paths to transformers
    transform: {
        '^.+\\.vue$': 'vue-jest',
        '^.+\\.js$': 'babel-jest',
        '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$':
            'jest-transform-stub'
    },

    // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
    transformIgnorePatterns: ['/node_modules/'],

    // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
    // unmockedModulePathPatterns: undefined,

    // Indicates whether each individual test should be reported during the run
    // verbose: undefined,

    // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
    // watchPathIgnorePatterns: [],

    // Whether to use watchman for file crawling
    // watchman: true,

    watchPlugins: [
        'jest-watch-typeahead/filename',
        'jest-watch-typeahead/testname'
    ]
};

Add a new one under the root directory__tests__Folder for storing test codes. Install Vue official@vue/test-utilsTo write test cases. Example:

import { shallowMount } from '@vue/test-utils';
import PopMessage from '@/components/PopMessage';
import { findFromWrapper } from '@/utils/test';

describe('Component - PopMessage', () => {
    It ('There should be a default slot ', () = >{
        const wrapper = shallowMount(PopMessage, {
            slots: {
                default: '<div data-test="pop-desc"></div>'
            }
        });

        const popDefault = findFromWrapper(wrapper, 'pop-desc');

        expect(popDefault.length).toBe(1);
    });

    It ('When the placement is not passed in, the default classname is pop left ', () = >{
        const wrapper = shallowMount(PopMessage);
        const popMessage = findFromWrapper(wrapper, 'pop-message').at(0);
        expect(popMessage.classes('pop-left')).toBe(true);
    });

    It ('When placement is right, there is a classname with pop right ', () = >{
        const wrapper = shallowMount(PopMessage, {
            propsData: { placement: 'right' }
        });
        const popMessage = findFromWrapper(wrapper, 'pop-message').at(0);
        expect(popMessage.classes('pop-right')).toBe(true);
    });

    It ('use maxwidth to control the longest width ', () = >{
        const wrapper = shallowMount(PopMessage, {
            propsData: {
                maxWidth: 200
            }
        });
        const popMessage = findFromWrapper(wrapper, 'pop-message').at(0);
        const styles = popMessage.attributes('style') || '';

        expect(/max-width: ?200px;?/.test(styles || '')).toBeTruthy();
    });

    It ('generate snapshot, determine structure ', () = >{
        const wrapper = shallowMount(PopMessage, {
            propsData: {
                placement: 'right',
                maxWidth: 200
            },
            slots: {
                default: '<div>Hello</div>'
            }
        });

        expect(wrapper).toMatchSnapshot();
    });
});

Finally, modify the scripts in the package. JSON file.

...
  "scripts": {
    "dev": "webpack-dev-server --color --config ./webpack.dev.conf",
    "build": "webpack --config ./webpack.prod.conf",
    "test": "jest",
    "lint": "eslint -c ./.eslintrc.js ./src --ext .js,.vue ./example --ext .js,.vue -f table --fix"
  },
  ...

So far, the Vue component development environment has been built. You can start writing happy code.
Complete example:Vue component – contains the configuration of eslint and jest

Publish to NPM

After you write and test the components you want to publish, you can prepare for publishing. First, you need to write a readme.md to introduce the purpose and use of your components in detail, and then you need to modify package.json. The following is the package. JSON file of a component published to NPM I wrote earlier.

{
  "Name": "Vue wechat PC", / * the name of the NPM package you are about to publish*/
  "Version": "0.0.12"*/
  "Description": "PC wechat display component for Vue. JS.", / * brief description of the project*/
  "Main": "dist / main. JS"*/
  "directories": {
    "example": "example"
  },
  "scripts": {
    "dev": "webpack-dev-server --color --config ./webpack.dev.conf",
    "build": "webpack --config ./webpack.prod.conf",
    "test": "jest",
    "lint": "eslint -c ./.eslintrc.js ./src --ext .js,.vue ./example --ext .js,.vue -f table --fix"
  },
  "Repository": {/ * git repository*/
    "type": "git",
    "url": "git+https://github.com/M-FE/vue-wechat-pc.git"
  },
  "Keywords": [/ * keywords "*/
    "vue.js",
    "pc",
    "wechat",
    "component"
  ],
  "Files": [/ * this is very important. Define the files published to NPM, and other files will not be published to NPM*/
    "dist"
  ],
  "devDependencies": {
  ...
  },
  "dependencies": {
    "moment": "^2.24.0",
    "vue": "^2.6.11"
  },
  "author": "Willem Wei < [email protected] >", / * author name*/
  "license": "ISC",
  "homepage": " https://github.com/M-FE/vue-wechat-pc#readme ", / * home page*/
  ...
}

After you write the package. JSON file, submit a commit using git to ensure that there is no content in the temporary storage area and editing area. You need to goNPM official websiteRegister an account and enter it on the command linenpm loginEnter the account and password you just registered. After it is correct, you can enter itnpm publishPublish the written component to NPM. After success, you can see it on your NPM home page.
When you modify the package code and submit a commit to publish to NPM, first modify the version number of the package to be modified. You can use the following commands. It will automatically update the version value in package.json and submit a commit.

npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]

  • Major: major version number
  • Minor: minor version number
  • Patch: patch number
  • Premajor: prepare the master version
  • Prepatch: prepare the next version
  • Prerelease: pre release version

summary

The above is the whole process of configuration and release. To put it simply, we use git for code management, use webpack to package files, use eslint to standardize code, use jest to write unit tests, and finally use NPM to open source our code.
I hope it will help you~

Recommended Today

Application of observer pattern in design pattern in Ruby Programming

Observer mode(sometimes referred to as publish / subscribe mode) is softwareDesign patternA kind of.In this mode, a target object manages all its dependent observer objects and actively notifies when its own state changes.This is usually achieved by calling the methods provided by each observer. When implementing the observer mode, it should be noted that the […]