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
- When does the plugin work?
- 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.
babylon
The interpreter converts the code string toASTTrees, such asimport {uniq, extend, flatten, cloneDeep } from "lodash"
intoAST
The tree
babel-traverse
rightAST
Tree parsing traverses the path of the entire tree.- Plugin converts the new
AST
The tree. - 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
;
babel-core
providetransform
Method to convert the code string toAST
The treebabel-types
Provide various operationsAST
Node’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