Build the WebPack production environment

The Babel plug-in we wrote belongs to Babel-Loader, and Babel-Loader basically runs with the WebPack environment. So to check if the Babel plugin works, we have to build the WebPack environment.

The directory structure

|-- babel-plugin-wyimport
    |-- .editorconfig
    |-- .gitignore
    |-- package.json
    |-- README.md
    |-- build
    |   |-- app.be45e566.js
    |   |-- index.html
    |-- config
    |   |-- paths.js
    |   |-- webpack.dev.config.js
    |   |-- webpack.prod.config.js
    |-- scripts
    |   |-- build.js
    |   |-- start.js
    |-- src
        |-- index.js
Copy the code

webpack.prod.config.js

Configuration file, no code compression and confusion, mainly to facilitate the comparison of file content before and after compilation

'use strict' process.env.BABEL_ENV = 'production'; process.env.NODE_ENV = 'production'; const path = require('path'); const paths = require("./paths"); const fs = require('fs'); const webpack = require("webpack"); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const { WebPlugin } = require('web-webpack-plugin'); module.exports = { output: { path: paths.build, filename: '[name].[chunkhash:8].js', chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js', publicPath: "/" }, entry: { "app":path.resolve(paths.src, "index.js") }, resolve:{ extensions:[".js", ".json"], modules: ["node_modules", paths.src] }, module: { rules: [ { test:/\.css$/, include:paths.src, loader: ExtractTextPlugin.extract({ use: [ { options:{ root: path.resolve(paths.src, "images") }, loader: require.resolve('css-loader') } ] }) }, { test:/\.less$/, include:paths.src, use:[ require.resolve('style-loader'), { loader:require.resolve('css-loader'), options:{ root: path.resolve(paths.src, "images") } }, { loader: require.resolve('less-loader') } ] }, { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], loader: require.resolve('url-loader'), options: { limit: 1000, name: 'static/images/[name].[hash:8].[ext]', }, }, { test:/\.(js|jsx)$/, include:paths.src, loader: require.resolve("babel-loader"), options:{ presets:["react-app"], plugins:[ //["wyimport", {libraryName:"lodash"}] ], compact: true //cacheDirectory: true } }, { exclude: [ /\.html$/, /\.(js|jsx)$/, /\.css$/, /\.less$/, /\.json$/, /\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/ ], loader: require.resolve('file-loader'), options: { name: 'static/[name].[hash:8].[ext]', }, } ] }, plugins: [new ExtractTextPlugin('[name].[chunkhash:8].css'), new WebPlugin({// Output HTML file filename: 'index.html, / / the HTML dependent ` entry ` requires: [" app "]}),]}Copy the code

build.js

The startup file mainly calculates the size of the file content before and after compilation

const webpack = require('webpack'); const path = require('path'); const config = require('.. /config/webpack.prod.config'); const chalk = require('chalk'); const paths = require('.. /config/paths'); const fs = require("fs"); Const getDirSize = (rootPath, unit ="k") => {if (! fs.existsSync(rootPath)) { return 0; } let buildSize = 0; const dirSize = (dirPath) => { let files = fs.readdirSync(dirPath, "utf-8") files.forEach((files) => { let filePath = path.resolve(dirPath, files); let stat = fs.statSync(filePath) || []; if (stat.isDirectory()){ dirSize(filePath) } else { buildSize += stat.size } }) } dirSize(rootPath) let map = new Map([["k",(buildSize/1024).toFixed(2)+"k"], ["M",buildSize/1024/1024+"M"]]) return map.get(unit); } // Clear directory files const rmDir = (path, isDeleteDir) => {if(fs.existssync (path)) {files = fs.readdirsync (path); files.forEach(function(file, index) { var curPath = path + "/" + file; if(fs.statSync(curPath).isDirectory()) { // recurse rmDir(curPath); } else { // delete file fs.unlinkSync(curPath); }}); fs.rmdirSync(path); }} const measureFileBeforeBuild = () => {console.log(' Size of build folder before packing: ${chalk. Green (getDirSize(paths.build))}\n ') rmDir(paths.build) Console. log(' chalk. Green (' packing done \n ')) console.log(' after packing folder size :${chalk. Green (getDirSize(paths.build))}\t Spend time: ${chalk.green((stats.endTime-stats.startTime)/1000)}s`) }, err => { console.log(chalk.red('Failed to compile.\n')); console.log((err.message || err) + '\n'); process.exit(1); }) } const build = () => { const compiler = webpack(config) return new Promise((resolve, Reject) => {compiler.run((err, stats) => {console.log(chalk. Green (" Start packing... )) if (err) { return reject(err); } const message = stats.toJson({}, true) if (message.errors.length) { return reject(message.errors); } return resolve(stats) }) }) } measureFileBeforeBuild()Copy the code

A profound

We type in the SRC /index.js file

 import { uniq } from "lodash"
Copy the code

Then NPM runs build

531k
lodash

 //import { uniq } from "lodash"
 import uniq from "lodash/uniq"
Copy the code

Then NPM runs build

If a file is introduced into Lodash there are many methods such as

import uniq   from "lodash/uniq";
import extend from "lodash/extend";
import flatten from "lodash/flatten";
import cloneDeep from "lodash/cloneDeep"; .Copy the code

Import {uniq, extend, flatten, cloneDeep} from “lodash” and also load on demand. It’s easy, just compile it and output it

import uniq   from "lodash/uniq";
import extend from "lodash/extend";
import flatten from "lodash/flatten";
import cloneDeep from "lodash/cloneDeep";
Copy the code

It is ok

Knowledge to prepare

Before writing a plugin, we need to be aware of the following two points

  1. When does the plugin work?
  2. How does plugin work

Webpack compilation principles

Babel-loader acts as a loader for Webpack. First of all, we need to understand the compilation process of Webpack and the role of loader in Webpack. Here is an article that says good, you should read it first and then read it down

The basic concept of Babel

Zhihu has a very clear article about Babel. If you are not very clear about Babel, go ahead and read it.

The main thing I want to emphasize here is the configuration of the Babel parameter. If I write a Babel plug-in called fiveOne, I configure it this way in the parameter

    {
        presets:["react-app"."es2015"],
        plugins:[
            ["fiveone", {libraryName:"lodash"}],
            ["transform-runtime"} fiveOne ->transform-runtime->es2015->react-appCopy the code

Plugins are compiled from left to right and presets are compiled from right to left

Babel compilation principles

The previous two sections explained when the plugin works. Now explain how the plugin works.

  1. babylonThe interpreter converts the code string toASTTrees, such asimport {uniq, extend, flatten, cloneDeep } from "lodash"intoASTThe tree
  2. babel-traverserightASTTree parsing traverses the path of the entire tree.
  3. Plugin converts the newASTThe tree.
  4. Outputs the new code string literature address

The plugin we will write is in step 3. Convert new AST tree by path? Here’s how to start step 3!

Start the Babel – the plugin

First we need to install two tools babel-core and babel-types;

npm install --save babel-core babel-types;

  1. babel-coreprovidetransformMethod to convert the code string toASTThe tree
  2. babel-typesProvide various operationsASTNode’s tool library

We type in SRC /index.js

var babel = require('babel-core');
var t = require('babel-types');
const code = `import {uniq, extend, flatten, cloneDeep } from "lodash"`;
const visitor = {
    Identifier(path){
        console.log(path.node.name)
    }
}
const result = babel.transform(code, {
    plugins: [{
        visitor: visitor
    }]
})
Copy the code

Run node SRC index.js

visitor

Babel traverses the AST tree, providing a method called a visitor object to access a stage, such as the one above

    Identifier(path){
        console.log(path.node.name)
    }
Copy the code

The AST tree expands as follows

uniq
src/index.js
Identifier

Identifier:{enter(path) {console.log(" I entered: ",path.node.name)}, exit(path) {console.log(" I entered: ",path.node.name)}}Copy the code

Run node SRC index.js

Traverse process: Traverse down - enter UNIQ -> Exit UNIQ -> traverse up - Enter UNIQ -> Exit UNIQ

path

Path represents the connection between two nodes. Through this object, we can access the current node, child node, parent node, add, delete, modify, replace, and so on. The following shows how to replace _uniq with uniq:

var babel = require('babel-core');
var t = require('babel-types');
const code = `import {uniq, extend, flatten, cloneDeep } from "lodash"`;

const visitor = {
    Identifier(path){
        if (path.node.name == "uniq") {
            var newIdentifier = t.identifier('_uniq'}}} const result = babel.transform(code, const result = babel.transform(code, const result = babel.transform) { plugins: [{ visitor: visitor }] }) console.log(result.code) //import { _uniq, extend, flatten,cloneDeep } from "lodash";
Copy the code

start

With that in mind we now convert the code string import {uniq, extend, flatten, cloneDeep} from “lodash” to

import uniq   from "lodash/uniq";
import extend from "lodash/extend";
import flatten from "lodash/flatten";
import cloneDeep from "lodash/cloneDeep";
Copy the code

The following code

var babel = require('babel-core');
var t = require('babel-types');
const code = `import {uniq, extend, flatten, cloneDeep } from "lodash"`;

const visitor = {
    ImportDeclaration(path, _ref = {opts:{}}){
        const specifiers = path.node.specifiers;
        const source = path.node.source;
        if(! t.isImportDefaultSpecifier(specifiers[0]) ) { var declarations = specifiers.map((specifier, I) => {// iterate over uniq extend flattencloneDeep
                returnT.iportdeclaration (// Create importImportDeclaration node [T.iPortDefaultSpecifier (specifier.local)], T.ingLiteral ('${source.value}/${specifier.local.name}`)
                )
            })
            path.replaceWithMultiple(declarations)

        }
    }
}

const result = babel.transform(code, {
    plugins: [{
        visitor: visitor
    }]
})
console.log(result.code)
Copy the code

Then the node SRC/index. Js

KO
AST

Configuration to node_modules

We’ll call this plugin fiveone so inside node_modules we’ll create a new folder called babel-plugin-fiveone

babel-plugin-fiveone/index.js

var babel = require('babel-core');
var t = require('babel-types'); ImportDeclaration(path, _ref = {opts:{}}){const speciFIERS = path.node.specifiers; constsource = path.node.source;
        if(! T.i sImportDefaultSpecifier (specifiers [0]) {var declarations = specifiers. The map ((specifiers) = > {/ / traverse uniq extend flattencloneDeep
                returnT.iportdeclaration (// Create importImportDeclaration node [T.iPortDefaultSpecifier (specifier.local)], T.ingLiteral ('${source.value}/${specifier.local.name}`)
                )
            })
            path.replaceWithMultiple(declarations)

        }
    }

};
module.exports = function (babel) {
    return {
        visitor
    };
}

Copy the code

Then modify the babel-loader configuration item in webpack.prod.config.js

options:{
    presets:["react-app"],
    plugins:[
        ["fiveone"}, {}]],Copy the code

Then enter SRC /index.js

import {uniq, extend, flatten, cloneDeep } from "lodash"
Copy the code

npm run build

According to the need to load

However, not all libraries can be transcoded so add lib to babel-loader plugin

options:{
    presets:["react-app"],
    plugins:[
        ["fiveone", {libraryName:"lodash"}}]],Copy the code

Change to babel-plugin-fiveone/index.js


var babel = require('babel-core');
var t = require('babel-types'); ImportDeclaration(path, _ref = {opts:{}}){const speciFIERS = path.node.specifiers; constsource= path.node.source; // Transcode only when libraryName is satisfiedif(_ref.opts.libraryName == source.value && (! T.i sImportDefaultSpecifier (specifiers [0]))) {/ / _ref opts is incoming parameters var declarations = specifiers. The map ((specifiers) = > { Uniq extend flattencloneDeep
                returnT.iportdeclaration (// Create importImportDeclaration node [T.iPortDefaultSpecifier (specifier.local)], T.ingLiteral ('${source.value}/${specifier.local.name}`)
                )
            })
            path.replaceWithMultiple(declarations)

        }
    }

};
module.exports = function (babel) {
    return {
        visitor
    };
}
Copy the code

The end of the

If there are some problems in the article, please correct them, thank you very much! Github address: github.com/Amandesu/ba… If you have gained something, you can give a star to us. Thank you very much!

Refer to the link

  • Webpack compilation principles
  • A beginner’s guide to Babel
  • Babel compilation principles
  • AST
  • babel-types
  • babel-plugin-handbook
  • How do I upload an NPM package