AST定义了代码的结构,经过操纵这颗语法树,能够精准的定位到声明语句、赋值语句、运算语句等等,实现对代码的分析、优化、变动等操做,主要有如下用途:javascript
JavaScriptParser是把js源码转化为抽象语法树的解析器。经常使用的JavaScript Parser:css
其中webpack就是使用的acorn将源代码解析成AST进行操做。html
loader是webpack用来处理加载不一样资源文件的插件,它只在webpack对资源文件进行加载阶段使用。java
从前面的文章webpack由浅入深——(webapck简易版)能够知道,loader的本质是一个函数。node
getSource(modulePath) {
let source = fs.readFileSync(modulePath, 'utf8');
//获取webpack.config.js中的rules
let rules = that.options.module.rules;
//遍历rules调用loader
for (let i = 0; i < rules.length; i++) {
let rule = rules[i];
// 用rule的test中正则匹配文件的类型是否须要使用laoder
if (rule.test.test(modulePath)) {
//获取rule中的loaders,例如['style-laoder','css-loader']
let loaders = rule.use;
let length = loaders.length; //loader的数量
let loaderIndex = length - 1; // 往右向左执行
// loader遍历器
function iterateLoader() {
let loaderName = loaders[loaderIndex--];
//loader只是一个包名,须要用require引入
let loader = require(join(that.root, 'node_modules', loaderName));
//使用loader,能够看出loader的本质是一个函数
source = loader(source);
if (loaderIndex >= 0) {
iterateLoader();
}
}
//遍历执行loader
iterateLoader();
break;
}
}
return source;
}
复制代码
因此loader的结构通常为:jquery
module.exports = function (source) {
//TODO须要执行的逻辑
}
复制代码
import { flatten,concat } from "lodash"
console.log(flatten([1,2],[3,4,[5,6]]));
console.log(contcat([1,2],[3,4]));
复制代码
import flatten from "lodash/flatten"
import concat from "lodash/concat"
console.log(flatten([1,2],[3,4,[5,6]]));
console.log(contcat([1,2],[3,4]));
复制代码
npm install babel-core babel-types -D
复制代码
//mode_modules/babel-plugin-babel-import
let babel = require('babel-core');
let types = require('babel-types');
const visitor = {
ImportDeclaration:{
enter(path,state={opts:{}}){
const specifiers = path.node.specifiers;
const source = path.node.source;
//加载的是lodash而且经过{xxx,xxx}的形式加载
if(state.opts.library == source.value && !types.isImportDefaultSpecifier(specifiers[0])){
const declarations = specifiers.map((specifier,index)=>{
return types.ImportDeclaration(
[types.importDefaultSpecifier(specifier.local)],
types.stringLiteral(`${source.value}/${specifier.local.name}`)
)
});
//替换原来的节点
path.replaceWithMultiple(declarations);
}
}
}
}
module.exports = function(babel){
return {
visitor
}
}
复制代码
const path = require("path");
const fs = require("fs");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist")
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {
plugins: [["babel-import", { library: "lodash" }]]
}
}
}
]
},
resolve: {},
plugins: [],
devServer: {}
};
复制代码
插件向第三方开发者提供了webpack引擎中完整的能力。使用阶段式的构建回调,开发者能够引入自定义插件到webpack构建流程中,几乎可以任意更改webpack编译结果。webpack
对象 | 钩子 |
---|---|
Compiler | run,compile,compilation,make,emit,done |
Compilation | buildModule,normalModuleLoader,succeedModule,finishModule,seal,optimize,after-seal |
Module Factory | beforeResolver,afterResolver,module,parser |
Parser | program,statement,call,expression |
Template Factory | hash,bootstrap,localVars,render |
从前面的文章webpack由浅入深——(webapck简易版)能够知道,其实插件是往钩子中注册回调的函数。git
//../lib/Compiler
class Compiler {
constructor(options){
this.options = options;
this.hooks = {
entryOption: new SyncHook(),
afterPlugins: new SyncHook(),
run: new SyncHook(),
beforeCompile: new SyncHook(),
afterCompile: new SyncHook(),
emit: new SyncHook(),
afterEmit: new SyncHook(),
done: new SyncHook(),
}
}
.....
}
复制代码
#! /usr/bin/env node
const path = require('path');
const fs = require('fs');
const root = process.cwd();
const Compiler = require('../lib/Compiler');
let options = require(path.resolve('webpack.config.js'));
let compiler = new Compiler(options);
compiler.hooks.entryOption.call(); //触发entryOptions
let {plugins} = options; //获取webpack.config.js中的plugns进行注册
plugins.forEach(plugin => {
plugin.apply(compiler)
});
compiler.hooks.afterPlugins.call(), //触发afterPlugins
compiler.run();
复制代码
因此简单插件的格式通常为:github
class xxxxPlugin{
//new xxxxPlugin(options)
constructor(options) {
this.options=options;
}
apply(compiler) {
//往钩子上注册回调
compiler.hooks.xxxx.tap('xxxxPlugin', ()=> {
//TODO执行的逻辑
});
}
}
module.exports=xxxxPlugin;
复制代码
前篇webpack由浅入深——(webpack优化配置)中提到了external来cdn引用第三方库从而减少文件体积,可是存在一个问题,必须手动在模板的html文件中预先写好script标签引入第三方的cdn,AutoExternalPlugin实现自动插入script。web
const path = require("path");
const fs = require("fs");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const AutoExternalPlugin = require("./plugin/AutoExternalPlugin");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist")
},
module: {
rules: []
},
resolve: {},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html"
}),
new AutoExternalPlugin({
jquery: {
varName: "jQuery",
url: "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"
}
})
],
devServer: {
contentBase: path.resolve(__dirname, "dist"),
host: "localhost",
port: 3000
}
};
复制代码
/*
1. 分析import xxxx语句是否引用了特定的模块
2. 自动往html中插入一个script标签,src就等于cdn地址
3. 生成模块的时候,若是是插件配置的模块生成一个外部模块返回
*/
const ExternalModule = require("webpack/lib/ExternalModule");
class AutoExternalPlugin {
constructor(options) {
this.options = options;
//记录外部模块
this.externalModules = {};
}
apply(compiler) {
//normalModuleFactory普通模块工厂,
compiler.hooks.normalModuleFactory.tap('AutoExternalPlugin', (normalModuleFactory) => {
normalModuleFactory.hooks.parser
.for('javascript/auto')
.tap('AutoExternalPlugin', parser => {
//当语法拿到会遍历语法树,当遍历到import节点的时候会
//statement就是import $ from 'jquery'语句
//source是'jquery'的文件路径 ;
parser.hooks.import.tap('AutoExternalPlugin', (statement, source) => {
//jquery模块要变成外部模块
if (this.options[source]) {
this.externalModules[source] = true;
}
});
})
//factory是一个工厂,完成建立模块的工做
normalModuleFactory.hooks.factory.tap('AutoExternalPlugin', factory => (data, callback) => {
const dependency = data.dependencies[0];
let value = dependency.request;//jquery
//须要转成外部模块,执行这里的逻辑
if (this.externalModules[value]) {
//let $ = window.jQuery;
callback(null, new ExternalModule(this.options[value].varName, 'window'));
//不然执行正常的工厂方法,默认建立一个普通的模块
} else {
factory(data, callback);
}
});
});
compiler.hooks.compilation.tap('InlinePlugin', (compilation) => {
compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('InlinePlugin', (htmlData, callback) => {
Object.keys(this.externalModules).forEach(key => {
htmlData.body.unshift({
tagName: 'script',
closeTag: true,
attributes: { type: 'text/javascript', src: this.options[key].url }
});
});
callback(null, htmlData);
});
});
}
}
module.exports = AutoExternalPlugin;
复制代码
webpack系列文章已经完结,后面会持续增长和修改内容。