咱們編寫的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不勝感激!
參考連接