A friend recently did a front-end engineering share, the author before just dabble, listen to the harvest quite a lot. Considering that engineering can systematically improve its front-end capabilities, the author has recently started this series, learning practices from scratch in the order of basic structure, specifications, tools, optimization, automated deployment and performance monitoring. Just division I let our group began to do technology sharing, the author shared a just learned ploP. Share, communicate, sort it out and post it for notes. In this paper, the source code

I. Overview of PLOP

What is plop? A mini-generator framework for generating template files that conform to team specifications.

It’s easy to think of it as a simple blend of the Inquirer dialog and the Hanldebar template.

Generate a template file? Sounds like a similar user snippet to vsCode. If you’re dealing with single files, they’re pretty much the same. Plop benefits from handlebars’ more powerful semantic templates, perhaps slightly more powerful than code snippets. So, is that all? Let’s try it out.

2. Start of PLOP

  1. The installation

    npm install --save-dev plop
    //npm install -g plop
    Copy the code
  2. Create plopfile.js in the project root folder

    //plopfile.js
    module.exports = function (plop) {};
    Copy the code

    It exports a function that takes a plop object as its first argument.

  3. Creating a generator

    A plop object exposes a plop API that contains the setGenerator(name, config) function. SetGenerator is used to create a generator. When ploP is run in the terminal of the current folder or subfolder, these generators will be displayed in the terminal as a list.

    Here is a simple generator:

    module.exports = function (plop) {
    	// Create a generator
    	plop.setGenerator('component', {
    		description: 'Add a common component'.// describe what is displayed after the generator in the terminal
    		prompts: [].// Prompts to capture user input
    		actions: []  // Behavior, specific implementation of the content
    	});
    };
    Copy the code

    An Inquirer is an Inquirer’s prompt. They can be seen in Inquirer’s documentation but are not needed here.

    The standard attributes of actions are as follows:

    attribute The data type The default value describe
    type String Type of action (includingadd.modify.addMany
    force Boolean false To enforce(Different action types correspond to different logic)
    data Object/Function {} Show that an action should be taken with data that prompts an answer
    abortOnFail Boolean true When an action fails for some reason, all subsequent actions are stopped
    skip Function A function that specifies whether the current action should be executed

    These are the basic, core elements of PloP. With that in mind, we can start to use it.

3. Initial experience of PLOP

The following uses a new VUE file in the VUE project as an example to demonstrate how to use plop. The default plopfile.js is no longer used because many files will be used later.

  1. Create a new folder in the project root directorygeneratorsAdd a new file index.js to the folder

    There are many subsequent files, so we will add a new folder to store relevant files

    //generators/index.js
    module.exports =  (plop) = > {
    	// Create a generator
    	plop.setGenerator('component', {
    		description: 'Add a common component'.// describe what is displayed after the generator in the terminal
    		prompts: [].// Prompts to capture user input
    		actions: []  // Behavior, specific implementation of the content
    	});
    };
    Copy the code
  2. Modify the package. The json

    Run the –plopfile command to specify that the plop entry file address is newly created

    //package.json
    {
      / /...
      "scripts": {
        / /...
        "generator": "plop --plopfile generators/index.js",}}Copy the code
  3. newhanldebarTemplate file

    Create a new index.vue. HBS file under generators folder

    <template>
      <div></div>
    </template>
    
    <script>
    export default {
      name: "{{ componentName }}",
      components: {},
      props: {
        list: {
          type: Array,
          default: function()
           {
            return [];
          },
        },
      },
      data() {
        return {}
      },
      created() {},
      mounted() {},
      methods: {},
    };
    </script>
    
    <style scoped></style>
    Copy the code
  4. Perfect generator

    //generators/index.js
    const componentExists = require('./utils/componentExists');
    //componentExists is a utility method that verifies that the same name already exists
    module.exports =  (plop) = > {
    	// Create a generator
    	plop.setGenerator('component', {
    		description: 'Add a component'.prompts: [{type: 'input'.name: 'componentName'.message: 'Please enter component name:'.default: 'Button'.validate: value= > {
    					if (+ /. /.test(value)) {
    						return componentExists(value)
    							? 'Same container name or component name already exists'
    							: true;
    					}
    					return 'Component name Required'; }},].actions: data= > {
    			const actions = [
    				{
    					type: 'add'.path: '.. /src/components/{{properCase componentName}}/index.vue'.Prompts. //componentName prompts
    					templateFile: './index.vue.hbs'.abortOnFail: true,},];returnactions; }}); };//utils/componentExists.js
    /** * componentExists ** Determines whether a component or page exists */
    const fs = require("fs");
    const path = require("path");
    const pageComponents = fs.readdirSync(
      path.join(__dirname, ".. /.. /src/components"));const pageContainers = fs.readdirSync(
      path.join(__dirname, ".. /.. /src/views"));const components = pageComponents.concat(pageContainers);
    
    function componentExists(comp) {
      return components.indexOf(comp) >= 0;
    }
    module.exports = componentExists;
    Copy the code
  5. In actual combat

    This is where we can give it a real try.

    First run the NPM Run Generator and then enter the component name Test

    The creation is complete. Try the component name with no input, empty input, and repeated input, and you won’t have to put a screenshot here.

    So much for today’s sharing…… No way! Only these words are not far away, obviously a CV can solve the matter. The following is a formal enterprise-level combat (brag (= – =)).

4. Plop progression

  1. What do we do when we create a new page

    Generally speaking, when we add a new page, we will create a new page folder under the views/ Pages folder (when the project is large, it will be divided into modules and another layer of folder), then create a new page vUE file, and then configure routing, add the corresponding VUex module, introduce encapsulated API and so on…

    So let’s try to do some of these things with PloP.

    / / Create a component folder in the generators folder and put the previous generator configuration and template files there. Add a new view folder for subsequent files. Generators /index.js file is adjusted as follows:

    const componentGenerator = require('./component/index.js');
    const viewGenerator = require('./view/index.js');
    
    module.exports = (plop) = > {
    	plop.setGenerator('component', componentGenerator);
    	plop.setGenerator('view', viewGenerator);
    };
    Copy the code

    Add index.vue. HBS file to view folder as above.

    The view/index.js file is adjusted as follows:

    const componentExists = require('.. /utils/componentExists');
    
    module.exports = {
    	description: 'Add a View container (page)'.prompts: [{type: 'input'.name: 'viewName'.message: 'Please enter container (page) name:'.default: 'Form'.validate: (value) = > {
    				if (+ /. /.test(value)) {
    					return componentExists(value)
    						? 'Same container name or component name already exists'
    						: true;
    				}
    				return 'Container name Required'; }},].actions: (data) = > {
    		let actions = [
    			{
    				type: 'add'.path: '.. /src/views/{{ viewName }}/index.vue'.templateFile: './view/index.vue.hbs'.abortOnFail: true,},];returnactions; }};Copy the code

    So far, the view and Component generators have the same functionality.

  2. Route processing when creating a page

    Create a router folder under SRC:

    //router/index.js
    import Vue from 'vue';
    import Router from 'vue-router';
    import Allrouters from './routers.js';
    Vue.use(Router);
    export default new Router({
    	mode: 'hash'.routes: [
    		// Default home page
    		{ path: The '*'.redirect: '/index' },
    		...Allrouters,
    	],
    });
    
    //router/routers.js
    
    //-- append import here --
    export default [
    	//-- append router here --
    ];
    Copy the code

    Note that //– append import here — and //– append router here — are exactly the same as re validation in the action and template files below.

    Create a router.js. HBS template file and add two modify actions:

    //view/router.js.hbs
    {
    		path: '/{{ viewName }}'.name: '{{ viewName }}'.component: {{ viewName }},
    },
    //-- append router here --
    
    //view/index.js  .actions: (data) = > {
    		let actions = [
    			{
    				type: 'add'.path: '.. /src/views/{{ viewName }}/index.vue'.templateFile: './view/index.vue.hbs'.abortOnFail: true}, {type: 'modify'.path: '.. /src/router/routers.js'.pattern: /(\/\/-- append import here --)/gi,
    				template:
    					"const {{ viewName }} = () => import('.. /views/{{ viewName }}/index.vue'); \n$1"}, {type: 'modify'.path: '.. /src/router/routers.js'.pattern: /(\/\/-- append router here --)/gi,
    				templateFile: './view/router.js.hbs',},];returnactions; },...Copy the code

    The two modify actions modify the phones. js file to synchronize routes. By default, the page is not divided into modules. If it is divided into modules, add a prompt for input module name, modify the corresponding logic, and add another layer. If routing is also divided into modules, it can be implemented in conjunction with webpack require.context, which can be dynamically adjusted according to the actual situation of the project.

  3. Configuration vuex

    I referred to the structure and specification modified by a friend of VUEX. It is not necessary to use the one I am most familiar with. Look at the code

    Add a template file corresponding to the above figure in the View folder. See template

    Add a prompt of type confirm, associated with the template action:

    //view/index.js
    prompts: [
    		/ /...
    		{
    			type: 'confirm'.name: 'vuex'.default: true.message: 'Vuex? '],},actions: (data) = > {
    		let actions = [
    			/ /...
    		];
    		if (data.vuex) {
    			let store = [
    				{
    					type: 'add'.path: '.. /src/views/{{ viewName }}/store/constants.js'.templateFile: './view/constants.js.hbs'.abortOnFail: true}, {type: 'add'.path: '.. /src/views/{{ viewName }}/store/actions.js'.templateFile: './view/actions.js.hbs'.abortOnFail: true}, {type: 'add'.path: '.. /src/views/{{ viewName }}/store/getters.js'.templateFile: './view/getters.js.hbs'.abortOnFail: true}, {type: 'add'.path: '.. /src/views/{{ viewName }}/store/mutations.js'.templateFile: './view/mutations.js.hbs'.abortOnFail: true}, {type: 'add'.path: '.. /src/views/{{ viewName }}/store/index.js'.templateFile: './view/index.js.hbs'.abortOnFail: true,},]; actions = actions.concat(store); }return actions;
    	},
    Copy the code

    Prompt can be taken as a template file, with handlebars’ own template grammar, according to the user to select a dynamic control template, modify the page template file index.vue. HBS as follows:

    //view/index.vue.hbs
    
    <template>
      <div></div>
    </template>
    
    <script>
    {{#if vuex}}
    import {
    	STORE_NAME,
    	GET_TOTAL_ACTION,
    	SET_TOTAL_MUTATION
    } from './store/constants';
    import storeOption from './store/index';
    import { mapState, mapActions, mapMutations } from 'vuex';
    {{/if}}
    
    export default {
      name: "{{ viewName }}", components: {}, props: { list: { type: Array, default: function() { return []; }, }, }, data() { return {}; }, computed: {{{#if vuex}}. mapState({total: (state) = > state[STORE_NAME].total,
    		}),
      {{/if}}
    		
    	},
      {{#if vuex}}
      beforeCreate(){
        this.$store.registerModule(STORE_NAME,storeOption)// Dynamically register the VUEX module
      },
      {{/if}}
      created() {},
      mounted() {},
      methods: {
        {{#if vuex}}. mapActions({getToalAction: `${STORE_NAME}/${GET_TOTAL_ACTION}`
    		}),
    		...mapMutations({
    			setToalMutation: `${STORE_NAME}/${SET_TOTAL_MUTATION}`,}).{{/if}}
      },
      {{#if vuex}}
      destroyed(){
        this.$store.unregisterModule(STORE_NAME,storeOption)// Uninstall the VUex module when you leave the page. Otherwise, the vuex action will be triggered several times when you re-enter the page
      },
      {{/if}}
    };
    </script>
    <style scoped></style>
    Copy the code
  4. Is this it?

    It just feels like… Yeah, okay?

    Consider that the routing file we modified is probably a public file, and other colleagues may add changes to it. In order to avoid conflicts, it is best to push to Git immediately after creating a new page. Node can execute script commands. Can git operations be handled as well? Give it a try.

    Create polP action from exec

    //generator/index.js
    
    const { execSync } = require('child_process'); // Node's child process, execSync, which is a synchronized version of Exec, generates a shell and runs commands in that shell
    const componentGenerator = require('./component/index.js');
    const viewGenerator = require('./view/index.js');
    
    module.exports = (plop) = > {
    	plop.setGenerator('component', componentGenerator);
    	plop.setGenerator('view', viewGenerator);
    	plop.setActionType('git'.(answers, config) = > {
    		try {
    			execSync(`git pull`);
    			execSync(`git add .`);
    			execSync('git commit -m${config.file}"`);
    			execSync(`git push`);// These are only examples and do not represent real situations
    		} catch (err) {
    			throwerr; }}); };//view/index.js
    	actions.push({
    		type: 'git'.viewName: data.viewName,
    	});
    Copy the code

    The setActionType method allows you to customize actions(similar to Add or modify). The default parameters are as follows:

    parameter type describe
    answers Object Prompt
    config ActionConfig Configuration object for the Generator Action
    plop PlopfileApi The PLOp API for the plop file in which the action resides

    Thus, Plop or Node can do whatever we want it to do.

  5. other

    The first steps implement basic routing, vuex configuration, git commit. However, there must be more general configuration to be added in real projects. For example, the pages of our project have a uniform basic layout, and the templates are rich:

    //view/index.vue.hbs
    
    <template>
    	<div class="pagesDiv">
    		<page-little-menu
    			:title="[{name: 'data governance'}, {name: 'Problem device view ', path:{{ viewName }}},]"
    		/>
    		<div class="tableDivForNoCondition">
    		</div>
    	</div>
    </template>
    Copy the code

    For example, we have dynamic calculation of the height of the EL table and then adaptive mixins, if there is a table page can optionally inject templates;

    It is also necessary to create a components folder under the page folder and add a sample component, and then import the component in index.vue.

    The wrapped AXIos/FETCH apis are also copied in…

    There are too many to go into here. I think every old front-end er had the experience of new, new, new, CV, CV, CV, CV and then put together the page infrastructure. After using plop, you just need to enter the name of the page, select some configuration, and set up a page structure that conforms to the specifications of your project. It’s cool to think about it. Plop has functions that are helper, partial, etc. Don’t hesitate to start.

Five, the summary

The above implementation went from the most basic configuration of a template file to a skeleton view container generation (but I don’t want to write here, the rest is similar). I care more about its role in improving norms than its role in improving efficiency. Use it to keep the whole project consistent. The more views, data, tools, etc. are broken down, the more the project looks like it was written by the same person. This is so important for multi-team collaboration that you can quickly adapt to the code as you change or inherit it from your colleagues. You know exactly where problems are likely to occur or where you will need to change. However, when the project or team is not large enough, the more detailed the split, the more difficult it is to write, such as the vuEX structure above, it may need to modify multiple files every time it is added or modified. Merging files, removing constant Settings, and simplifying the structure may provide more of a ploP efficiency boost than a canonical one.

The scope of this article is limited to the page level, but if you zoom in and write in the configuration of the entire project, then upload NPM, it is a scaffold? Try it when this series is complete.

Reference:

A good PloP Chinese document

React from a Friend