在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
"bin": {"zfpxpack": "./bin/zfpxpack.js"},
#! /usr/bin/env node
const path = require('path');
const fs = require('fs');
const Compiler = require('../lib/Compiler');//引入Compiler构造函数
let root = process.cwd();
let options = require(path.resolve(root, 'webpack.config.js'));
let compiler = new Compiler(options);
compiler.hooks.entryOption.call(options);
compiler.run();
const { SyncHook } = require('tapable');
const path = require('path');
const fs = require('fs');
const esprima = require('esprima');
const estraverse = require('estraverse');
const escodegen = require('escodegen');
class Compiler {
constructor(options) {
this.options = options;//保存选项对象
this.hooks = {
entryOption: new SyncHook(['options']),//保存一个解析选项对象的钩子
run: new SyncHook([]),
afterPlugins: new SyncHook([]),
compile: new SyncHook(),//编译
afterCompile: new SyncHook(),//编译完成后
emit: new SyncHook(),//发射
done: new SyncHook()//完成
}
let plugins = options.plugins;//加载自定义插件
if (plugins && plugins.length > 0) {
plugins.forEach(plugin => {
plugin.apply(this);
});
}
this.hooks.afterPlugins.call();//发射加载完成事件
}
run() {
}
}
module.exports = Compiler;
const {SyncHook} = require('tapable');
const path = require('path');
const esprima = require('esprima');
const estraverse = require('estraverse');
const escodegen = require('escodegen');
const ejs = require('ejs');
const fs = require('fs');
class Compiler{
constructor(options){
this.options = options;
this.hooks = {
entryOption:new SyncHook(['compiler']),
afterPlugins:new SyncHook(['compiler']),
run:new SyncHook(['compiler']),
compile:new SyncHook(['compiler']),
afterCompile:new SyncHook(['compiler']),
emit:new SyncHook(['compiler']),
done:new SyncHook(['compiler'])
}
let plugins = options.plugins;
plugins.forEach((plugin)=>{
plugin.apply(this);
});
this.hooks.afterPlugins.call(this);
}
run(){
const compiler = this;
this.hooks.run.call(compiler);
let root = process.cwd();//webpack所在目录的绝对路
let {
entry,
output:{
path:dist,
filename
},
module:{
rules
}
} = this.options;//入口 输出 目录 和文件名
let modules = {};//存放所有的模块
let entryId; //入口ID 所有的ID都是相对于root根目录的
this.hooks.compile.call(compiler);
parseModule(path.resolve(root,entry),true);//解析模块
this.hooks.afterCompile.call(compiler);
let tmpl = fs.readFileSync(path.join(__dirname,'main.ejs'),'utf8');
let bundle = ejs.compile(tmpl)({modules,entryId});
fs.writeFileSync(path.join(dist,filename),bundle);
this.hooks.emit.call(compiler);
this.hooks.done.call(compiler);
function parseModule(modulePath,isEntry){//解析模块和依赖的模块 路径是一个绝对路径
let source = fs.readFileSync(modulePath,'utf8');//读取此模块的文件内容
for(let i=0;i<rules.length;i++){
let rule = rules[i];
if(rule.test.test(modulePath)){
let use = rule.use || rule.loader
if(use instanceof Array){
for(let j=use.length-1;j>=0;j--){
let loader = require(path.resolve(root,'node_modules',use[j]));
source = loader(source);
}
}else{
let loader = require(path.resolve(root,'node_modules',typeof use == 'string'?use:use.loader));
}
break;
}
}
let moduleId = './'+path.relative(root,modulePath);
if(isEntry) entryId = moduleId;
let parseResult = parse(source,path.dirname(moduleId));
let requires = parseResult.requires;//取得依赖的模块数组
modules[moduleId] = parseResult.source;//记录ID和转换后代码的对应关系
if(requires && requires.length>0){
requires.forEach(require=>{
parseModule(path.join(root,require))
});
}
}
//源码和父路径
function parse(source,parentPath){
let ast = esprima.parse(source);
let requires = [];
estraverse.replace(ast,{
enter(node,parent){
if(node.type == 'CallExpression' && node.callee.name == 'require'){
let name = node.arguments[0].value;//取得参数里的值
name += (name.lastIndexOf('.')>0?'':'.js');//添加后缀名
let moduleId = './'+path.join(parentPath,name);//取得此模块ID,也是相对于根路径的
requires.push(moduleId);
node.arguments = [{type:'Literal',value:moduleId}];
return node;
}
return node;
}
});
source = escodegen.generate(ast);//重新生成代码
return {source,requires};//转换后的代码和依赖的模块
}
}
}
module.exports = Compiler;
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function require(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, require);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // Load entry module and return exports
/******/ return require(require.s = "<%-entryId%>");
/******/ })
/************************************************************************/
/******/ ({
<%
for(let id in modules){
let source = modules[id];%>
/***/ "<%-id%>":
/***/ function(module, exports,require) {
eval(`<%-source%>`);
/***/ },
<%}%>
/******/ });
less-loader
var less = require('less');
module.exports = function (source) {
let css;
less.render(source, (err, output) => {
css = output.css;
});
return css.replace(/\n/g,'\\n','g');
}
css-loader
var less = require('less');
module.exports = function (source) {
return `let style = document.createElement('style');style.innerHTML = ${JSON.stringify(source)};document.head.appendChild(style);`;
}
const qiniu=require('qiniu');
const path=require('path');
const fs=require('fs');
class UploadPlugin{
constructor(options) {
this.options=options||{};
}
apply(compiler) {
compiler.hooks.afterEmit.tap('UploadPlugin', (compilation) => {
let {bucket='video',domain="img.zhufengpeixun.cn",accessKey='fi5imW04AkxJItuFbbRy1ffH1HIoo17HbWOXw5fV',secretKey='ru__Na4qIor4-V7U4AOJyp2KBUYEw1NWduiJ4Pby'}=this.options;
var mac=new qiniu.auth.digest.Mac(accessKey,secretKey);
var options = {
scope: bucket,
};
var putPolicy = new qiniu.rs.PutPolicy(options);
var uploadToken=putPolicy.uploadToken(mac);
var config = new qiniu.conf.Config();
console.log(compilation.assets);
let promises = Object.keys(compilation.assets).map(asset => upload(asset));
Promise.all(promises).then(function (data) {
console.log('发布CDN成功!');
});
function upload(filename) {
return new Promise(function (resolve,reject) {
var localFile=path.join(__dirname,'../build',filename);
var formUploader = new qiniu.form_up.FormUploader(config);
var putExtra = new qiniu.form_up.PutExtra();
var key=filename;
formUploader.putFile(uploadToken,key,localFile,putExtra,function (err,body,info) {
if (err)
reject(err);
else
resolve(body)
});
});
}
});
}
}
module.exports=UploadPlugin;
index.js
let name = require('./a/a1');
require('./index.less');
alert(name);
index.less
body{color:red;}
webpack.config.js
const path = require('path');
const AfterCompilerWebpackPlugin = require('./src/plugins/after-compiler-webpack-plugin');
const CompilerWebpackPlugin = require('./src/plugins/compiler-webpack-plugin');
const DoneWebpackPlugin = require('./src/plugins/done-webpack-plugin');
const EmitWebpackPlugin = require('./src/plugins/emit-webpack-plugin');
const OptionWebpackPlugin = require('./src/plugins/option-webpack-plugin.js');
const RunWebpackPlugin = require('./src/plugins/run-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve('dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.less/,
use: ['style-loader', 'less-loader']
}
]
},
plugins: [
new AfterCompilerWebpackPlugin(),
new CompilerWebpackPlugin(),
new DoneWebpackPlugin(),
new EmitWebpackPlugin(),
new OptionWebpackPlugin(),
new RunWebpackPlugin()
]
}
plugins
class Plugin{
apply(compiler) {
compiler.hooks.entryOption.tap('Plugin',(option) => {
console.log('run-webpack-plugin');
});
}
}
module.exports=Plugin;
class Plugin{
apply(compiler) {
compiler.hooks.entryOption.tap('Plugin',(option) => {
console.log('run-webpack-plugin');
});
}
}
module.exports=Plugin;
class Plugin{
apply(compiler) {
compiler.hooks.entryOption.tap('Plugin',(option) => {
console.log('compiler');
});
}
}
module.exports=Plugin;
class Plugin{
apply(compiler) {
compiler.hooks.entryOption.tap('Plugin',(option) => {
console.log('after compiler');
});
}
}
module.exports=Plugin;
class Plugin{
apply(compiler) {
compiler.hooks.entryOption.tap('Plugin',(option) => {
console.log('emit');
});
}
}
module.exports=Plugin;
class Plugin{
apply(compiler) {
compiler.hooks.entryOption.tap('Plugin',(option) => {
console.log('done');
});
}
}
module.exports=Plugin;