咱们编写的babel插件是所属于babel-loader,而babel-loader基本运行与webpack环境.因此为了检测babel插件的是否起做用,咱们必须构建webpack环境.css
|-- 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
复制代码
配置文件,没有对代码进行压缩和混淆,主要为了方便对比编译先后的文件内容html
'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({
//输出的html文件名称
filename: 'index.html',
//这个html依赖的`entry`
requires:["app"]
}),
]
}
复制代码
启动文件,主要计算编译先后的文件内容大小node
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);
}
// 清空目录文件
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(`打包以前build文件夹的大小: ${chalk.green(getDirSize(paths.build))}\n`)
rmDir(paths.build) //删除build文件夹
return build().then((stats) => {
console.log(chalk.green(`打包完成\n`))
console.log(`打包以后文件夹大小:${chalk.green(getDirSize(paths.build))}\t花费时间: ${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("开始打包..."))
if (err) {
return reject(err);
}
const message = stats.toJson({}, true)
if (message.errors.length) {
return reject(message.errors);
}
return resolve(stats)
})
})
}
measureFileBeforeBuild()
复制代码
咱们在src/index.js
文件里面输入react
import { uniq } from "lodash"
复制代码
而后 npm run build
webpack
//import { uniq } from "lodash"
import uniq from "lodash/uniq"
复制代码
而后 npm run build
git
若是一个文件引入lodash不少方法如github
import uniq from "lodash/uniq";
import extend from "lodash/extend";
import flatten from "lodash/flatten";
import cloneDeep from "lodash/cloneDeep";
...
复制代码
这样的写法就至关臃肿,那么能不能这么写import {uniq, extend, flatten, cloneDeep } from "lodash"
而且也实现按需载入呢? 很简单,只要将它编译输出成web
import uniq from "lodash/uniq";
import extend from "lodash/extend";
import flatten from "lodash/flatten";
import cloneDeep from "lodash/cloneDeep";
复制代码
就能够了npm
编写plugin以前首先咱们要清楚如下二点json
babel-loader
做为webpack
的一个loader
.首先咱们弄清楚webpack的编译过程和loader
在webpack
中做用 这里有一篇文章说很好,你们先去阅读理解以后再往下看
知乎有一篇文章讲得比较清楚,对babel不是很清楚的同窗先进去了解以后再往下看!
在这里,我主要想强调一下
babel
参数的配置,若是我写了一个名叫fiveone
的babel
插件,我在参数中这么配置
{
presets:["react-app", "es2015"],
plugins:[
["fiveone", {libraryName:"lodash"}],
["transform-runtime", {}]
],
}
起做用的顺序为fiveone->transform-runtime->es2015->react-app
复制代码
编译顺序为首先plugins
从左往右而后presets
从右往左
上面二节解释了plugin
在何时起做用,下面解释一下plugin
如何起做用?
babylon
解释器把代码字符串转化为AST树, 例如import {uniq, extend, flatten, cloneDeep } from "lodash"
转化为AST
树
babel-traverse
对AST
树进行解析遍历出整个树的path.AST
树.咱们要编写的plugin在第三步.经过path来转换出新的
AST
树?下面咱们就开始如何进行第三步!
首先咱们须要安装二个工具babel-core
和babel-types
;
npm install --save babel-core babel-types
;
babel-core
提供transform
方法将代码字符串转换为AST
树babel-types
提供各类操做AST
节点的工具库咱们在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
}]
})
复制代码
运行node src index.js
babel对AST
树进行遍历,遍历的过程会提供一个叫visitor对象的方法对某个阶段访问, 例如上面的
Identifier(path){
console.log(path.node.name)
}
复制代码
就是访问了Identifier节点,AST
树展开以下
uniq
,由于每一个节点进入和退出都会调用该方法。 遍历会有二次,一个是像下遍历进入,一个是像上遍历退出. 咱们将
src/index.js
中的
Identifier
方法改成
Identifier:{
enter(path) {
console.log("我是进入的:",path.node.name)
},
exit(path) {
console.log("我是进入的:",path.node.name)
}
}
复制代码
运行node src index.js
遍历流程: 向下遍历-进入uniq->退出uniq->向上遍历-进入uniq->退出uniq
path 表示两个节点之间的链接,经过这个对象咱们能够访问到当前节点、子节点、父节点和对节点的增、删、修改、替换等等一些操做。下面演示将uniq
替换_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') //建立一个名叫_uniq的新identifier节点
path.replaceWith(newIdentifier) //把当前节点替换成新节点
}
}
}
const result = babel.transform(code, {
plugins: [{
visitor: visitor
}]
})
console.log(result.code) //import { _uniq, extend, flatten, cloneDeep } from "lodash";
复制代码
有了以上概念咱们如今把代码字符串import {uniq, extend, flatten, cloneDeep } from "lodash"
转化成
import uniq from "lodash/uniq";
import extend from "lodash/extend";
import flatten from "lodash/flatten";
import cloneDeep from "lodash/cloneDeep";
复制代码
代码以下
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) => { //遍历 uniq extend flatten cloneDeep
return t.ImportDeclaration( //建立importImportDeclaration节点
[t.importDefaultSpecifier(specifier.local)],
t.StringLiteral(`${source.value}/${specifier.local.name}`)
)
})
path.replaceWithMultiple(declarations)
}
}
}
const result = babel.transform(code, {
plugins: [{
visitor: visitor
}]
})
console.log(result.code)
复制代码
而后node src/index.js
AST
上
代码写完了,起做用的话须要配置,咱们把这个插件命名为fiveone因此在node_modules里面新建一个名叫babel-plugin-fiveone的文件夹
babel-plugin-fiveone/index.js
中输入
var babel = require('babel-core');
var t = require('babel-types');
const visitor = {
// 对import转码
ImportDeclaration(path, _ref = {opts:{}}){
const specifiers = path.node.specifiers;
const source = path.node.source;
if (!t.isImportDefaultSpecifier(specifiers[0]) ) {
var declarations = specifiers.map((specifier) => { //遍历 uniq extend flatten cloneDeep
return t.ImportDeclaration( //建立importImportDeclaration节点
[t.importDefaultSpecifier(specifier.local)],
t.StringLiteral(`${source.value}/${specifier.local.name}`)
)
})
path.replaceWithMultiple(declarations)
}
}
};
module.exports = function (babel) {
return {
visitor
};
}
复制代码
而后修改webpack.prod.config.js
中babel-loader
的配置项
options:{
presets:["react-app"],
plugins:[
["fiveone", {}]
],
}
复制代码
而后src/index.js
中输入
import {uniq, extend, flatten, cloneDeep } from "lodash"
复制代码
npm run build
然而不能对全部的库都进入这么转码因此在babel-loader
的plugin增长lib
options:{
presets:["react-app"],
plugins:[
["fiveone", {libraryName:"lodash"}]
],
}
复制代码
在babel-plugin-fiveone/index.js
中修改成
var babel = require('babel-core');
var t = require('babel-types');
const visitor = {
// 对import转码
ImportDeclaration(path, _ref = {opts:{}}){
const specifiers = path.node.specifiers;
const source = path.node.source;
// 只有libraryName知足才会转码
if (_ref.opts.libraryName == source.value && (!t.isImportDefaultSpecifier(specifiers[0])) ) { //_ref.opts是传进来的参数
var declarations = specifiers.map((specifier) => { //遍历 uniq extend flatten cloneDeep
return t.ImportDeclaration( //建立importImportDeclaration节点
[t.importDefaultSpecifier(specifier.local)],
t.StringLiteral(`${source.value}/${specifier.local.name}`)
)
})
path.replaceWithMultiple(declarations)
}
}
};
module.exports = function (babel) {
return {
visitor
};
}
复制代码
若是文章有些地方有问题请指正,很是感谢! github地址:github.com/Amandesu/ba… 若是你们有所收获,能够随手给个star不胜感激!
参考连接