preface

Hi, I’m Brother Fly. I haven’t written in a long time. Recently, the group leader inadvertently raised a requirement, how to unify the number of references of components in the project? ? Hearing this, I immediately became interested and thought that I could use webpack loader to do it. At that time, the first idea in my mind was to use this to do it. Later, from the perspective of optimization, I developed a custom plugin which has been released to NPM

As shown in figure:

component

What can you learn from reading this article

  1. How to develop a WebPack plug-in
  2. How to package using rollup + TS
  3. Publish your own NPM

webpack

Webpack must be very familiar to everyone, interview every day asked, you will Webpack? Do you know how Webpack works? Here I will not say, there have been a lot of articles said, but read others write, do not go to write again, will never learn.

Handwriting webpack core principles

Github.com/wzf1997/web…

One of the two websites above is about the core principle of Webpack, and the other is a simple version of Webpack written by me, which is relatively simple to implement.

Let’s take a look at the general webPack build process:

  1. webpack

The first step is the initialization parameter phase

This step will read the corresponding configuration parameters from our configured webpack.config.js and merge the parameters passed in the shell command to get the final package configuration parameters

So webpack-Merge is typically used in real development.

The second step is to prepare for the compile phase

In this step we return a Compiler method by calling the webpack() method, create our Compiler object, and register each webpack Plugin. Find the entry code in the configuration entry and call the compiler.run() method to compile. I’m going to focus on this later

The third step is module compilation

The file is analyzed from the entry module and loaders of the matching file are called to process the file. At the same time, the module is analyzed and compiled recursively. Common loaders include TS-loader, Thread-loader, cache-loader, CSS-loader, and babel-loader. The essence of loader is to get the source, and after some processing, return it, which is conducive to the next loader to get the corresponding source, kind of like the chain call of promise. I’m not going to show you, I recommend this article,

loader

Webpack Loader juejin.cn/post/699275…

Steps four and five complete compiling and exporting the file

After the recursion is complete, each reference module is processed by loaders and the interdependence between modules is obtained. The output file stage cleans up module dependencies and outputs the processed files to the disk directory of Ouput.

Webpack plug-in

At this point, one might ask, where does webPack’s custom plug-in take place? The plug-in mechanism in Webpack is based on Tapable implementation decoupled from the packaging process, and all forms of plug-ins are based on Tapable implementation.

The Tapable package is essentially a library for us to create custom events and trigger custom events, similar to the EventEmitter Api in Nodejs.

So there are all kinds of hooks in WebPack, and now that we have these hooks, we can listen to webpack as it starts, as it finishes, as it does all the static resource packaging, and we can do something about it, and then there are all kinds of plug-ins on the market. In fact, it is worth thinking about the architecture mode of Webpack to see whether similar operations can be used in the future business.

Publish and subscribe model

Those of you who have been involved in webpack plug-in development, more or less, probably have. Every WebPack plug-in is a class (of course classes are essentially syntactic sugar of funciton), and every plug-in must have an Apply method.

The Apply method accepts a Compiler object. All we did above was call the apply method of the plugin passed in and pass in our Compiler object.

Here I ask you to keep in mind the above process, everyday we write the Webpack Plugin essentially by manipulating compiler objects to affect the packaging results.

apply

Now, if you still don’t understand this, maybe you’re a little confused, let’s go back to the second step above, preparing for the compilation phase, what does this preparation phase actually do

  1. Consolidation parameters
  2. Creating compiler objects
  3. It then loads the plug-in function, which iterates through the array of plug-ins, and then calls the Apply method for each plug-in instance

So let me write down the pseudo-code for you to understand

Function webpack(options) {// Merge arguments const mergeOptions = _mergeOptions(options); // Create compiler object const Compiler = new Compiler(mergeOptions); // Loadplugin _loadPlugin(options.plugins, compiler); return compiler; } // loadplugin function _loadPlugin(plugins, compiler) { if (plugins && Array.isArray(plugins)) { plugins.forEach((plugin) => { plugin.apply(compiler); }); } } module.exports = webpack;Copy the code

This should make sense to you, and explains why the WebPack plugin we write always has the Apply method.

The compiler then has a variety of hooks that allow you to write custom plugins based on your own needs.

Number of Component statistics

Demand background

Why do we have this thing? Technology is essentially a problem solver. We added the C-side game group to deposit a set of public game component library. In fact, it is to promote the design to standardize the unified style and reduce the time for design acceptance during the test week, otherwise it will be too painful to change the style every time. If we change the common component, one person will make no mistake if others use it. How do you know which components are referenced more often, encapsulate them, upgrade them, and have enough reason to drive the design to say, you’re using this component in so many businesses, and this is the same as before, greatly reducing development time. They also reduce the acceptance time, why not? So I did it. It wasn’t difficult, but it meant a lot.

Technical analysis

The first analysis of our common component reference is the following way

import { ViewPager, ViewPagerRef, Image } from '@growth/ui'
Copy the code

In fact, we can generate the AST process, let’s do a statistic, let’s look at the following graph:

AST

In fact, for us, I don’t need to change the generated results, I just need to do a statistics before production. Some students here may ask, how do I know what the lexical and grammatical analysis of the above will look like? Here is a website to share with you

AstExplorer.net/

Here I type in what we need

Ast parse

And then we’ll see this structure, and we’ll find our component name and the package name that we’re referring to, so I can listen to WebPack go through all the files, and as soon as it hits the corresponding node, we’ll store it, and that’s it.

I went to webpack official website at this time and we found this hook

Compiler uses the NormalModuleFactory module to generate various modules. Starting at the entry point, the module breaks down each request, parses the file contents for further requests, and then crawls the entire file by breaking down all requests and parsing new files. In the final phase, each dependency becomes an instance of the module.

The NormalModuleFactory class extends Tapable and provides a number of lifecycle hooks, basically from parsing to generation of the previous module. Here we find parser, which has import in the header of the file

parser

So this is actually esM for the type that we’re dealing with, or all of auto, so I’m going to select auto and I wrote down this code:

  apply(compiler: Compiler) {
    const parser = (factory: any) => {
      factory.hooks.parser.for('javascript/auto').tap('count-webpack-plugin', (parser: any) => {
      })
    }
    compiler.hooks.normalModuleFactory.tap('count-webpack-plugin', parser)
  }
Copy the code

Tap is a custom name that you can call whatever you want, just keep it globally unitary.

And we get the Parser which is actually a hook

You can see the image below:

parser HOOK

ImportSpecifier = importSpecifier = importSpecifier = importSpecifier = importSpecifier = importSpecifier = importSpecifier = importSpecifier

index.js

import _, { has } from 'lodash';
Copy the code

myPlugin

parser.hooks.importSpecifier.tap(
  'MyPlugin',
  (statement, source, exportName, identifierName) => {
    /* First call
    source == 'lodash'
    exportName == 'default'
    identifierName == '_'
  */
    /* Second call
    source == 'lodash'
    exportName == 'has'
    identifierName == 'has'
  */
  }
);
Copy the code

We want the last identifierName and the last source for the hook callback

The Source corresponds to the filename that our plugin passed in, so our callback will be executed as many times as there are matches during the whole Webpack construction process, so the result is still very accurate.

const parser = (factory: any) => { if (! this.opts.startCount) { return } factory.hooks.parser.for('javascript/auto').tap('count-webpack-plugin', (parser: any) => { parser.hooks.importSpecifier.tap('count-webpack-plugin', (_statement: string, source: string, _exportName: string, identifierName: string) => { if (source.includes(this.opts.pathname)) { this.total.len = this.total.len + 1 const key = identifierName this.total.components[key] = this.total.components[key] ? this.total.components[key] + 1 : 1 } }) }) }Copy the code

You know, it’s just data and you make a statistic, and then you show it,

compiler.hooks.normalModuleFactory.tap('count-webpack-plugin', parser)
compiler.hooks.done.tap('count-webpack-plugin-done',done)
Copy the code

So when apply also listens for the wrapping end hook, we can output the wrapping end control.

Chalk was introduced to give the console color without interfering with other packaged outputs

This is a screenshot of my statistics of our project components

And you end up with this

So it’s pretty straightforward.

Packaging and publishing

Okay, so the whole file is done, and I looked at it and I didn’t have 100 lines of code, so for packaging here, I chose rollup, which is treeShaking friendly as opposed to WebPack. And no hot yao heavy, for NPM package or JS-SDK to package feeling is also very convenient to use. Because we only need to package a simple bundle, we don’t need much other functionality. In our actual work, or flexible choice of corresponding tools.

Install the TS

yarn add typescript -D
Copy the code

Generate tsconfig.json file to configure compilation options for TS:

npx tsc --init
Copy the code

Tsconfig.json is then generated in the project root directory

Here is a recommended article for you:

Juejin. Cn/post / 684490… Master tsconfig. Json

Install a rollup

When it comes down to it, our code is ESM-compliant and written by TS. We’ll package it up and make it available to Node or the browser, so we can use rollup to process the code.

Create rollup.config.js in the project file

Since we are TS files, install rollup-plugin-typescript2 to process TS files

yarn add rollup-plugin-typescript2 -D
Copy the code

Next we define our input

const path = require('path') import ts from 'rollup-plugin-typescript2' const getPath = _path => path.resolve(__dirname, Pa const PKG = require('./package.json') const extensions = ['.js', '.ts', ] const tsPlugin = ts({tsconfig: getPath('./tsconfig.json'), // import tsconfig extensions) const outputs = [{file: Pkg. main, format: 'CJS '}, {file: pkg.module, format: 'esm'}] export default {input:' SRC /main.ts', Outputs, // Input file type plugins: [tsPlugin]}Copy the code

This is a simple configuration and we’re done. Here I’ll talk about package.json

pkg

Main corresponds to the NPM package entry file, usually common JS

The module field is used to import the NPM package. This file will be loaded first if esM is supported

Types are the corresponding packaged post-D.ts files

Then we do a package rollup

rollup -c
Copy the code

Then you’ll find the package discovery console

terminal

It’s what we rely on that’s not packed in

Take a look at the common JS directory:

content

Rollup will fail to package when the current module references other modules

 yarn add rollup-plugin-node-resolve -D
Copy the code

At some point, your project may depend on packages installed from NPM into the node_modules folder.

Unlike other bundled software such as Webpack and Browserify, Rollup doesn’t know how to handle these dependencies out of the box – we need to add some plug-in configuration.

By default, only ES6+ import/export is supported for modules in rollup.js compiled source code. However, a large number of NPM modules are based on CommonJS modules, which results in a large number of NPM modules cannot be compiled directly. This is why plug-ins that support NPM and CommonJS modules are compiled to assist rollup.js.

@rollup/plugin-commonjs
@rollup/plugin-node-resolve
Copy the code

Once configured to the plug-in list, you are ready.

release

With all the work done, how do I publish to the NPM package

  1. Go to the NPM website and register an account
  2. Then go to our current directory
npm login 
Copy the code

Just enter your account number and password

Then enter

npm publish
Copy the code

Before each release, modify the package.json version and release it

conclusion

This article analyzes the problem step by step from a simple small demand itself, and finally solves the problem. If you think the writing is good, welcome to try!!

Git repository:Github.com/wzf1997/cou…

For a ⭐️ star, there are bugs or requirements to contact me