1、目录结构

2、安装
cnpm install webpack webpack-cli babel-loader @babel/core @babel/preset-env -D
3、flow.js
let fs = require('fs');let path = require('path');const { SyncHook } = require("tapable");class Compiler {constructor(options) {this.options = options;this.hooks = {run: new SyncHook(),done: new SyncHook(),};}run() {this.hooks.run.call();let modules = [];let chunks = [];let files = [];// 确定入口:根据配置中的entry找出所有的入口文件let entry = path.join(this.options.context, this.options.entry);//从入口文件出发,调用所有配置的Loader对模块进行编译,let entryContent = fs.readFileSync(entry, "utf8");let entrySource = babelLoader(entryContent);let entryModule = { id: entry, source: entrySource };modules.push(entryModule);//再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理let title = path.join(this.options.context, "./src/title.js");let titleContent = fs.readFileSync(title, "utf8");let titleSource = babelLoader(titleContent);let titleModule = { id: title, source: titleSource };modules.push(titleModule);//根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunklet chunk = { name: "main", modules };chunks.push(chunk);//再把每个Chunk转换成一个单独的文件加入到输出列表let file = {file: this.options.output.filename,source: `(function (modules) {function __webpack_require__(moduleId) {var module = { exports: {} };modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);return module.exports;}return __webpack_require__("./src/app.js");})({"./src/app.js": function (module, exports, __webpack_require__) {var title = __webpack_require__("./src/title.js");console.log(title);},"./src/title.js": function (module) {module.exports = "title";},});`,};files.push(file);//在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统let outputPath = path.join(this.options.output.path,this.options.output.filename);fs.writeFileSync(outputPath, file.source,'utf8');this.hooks.done.call();}}//1.从配置文件和Shell语句中读取与合并参数,得出最终的参数let options = require('./webpack.config');//2.用上一步得到的参数初始化Compiler对象let compiler = new Compiler(options);//3.加载所有配置的插件if (options.plugins && Array.isArray(options.plugins)) {for (const plugin of options.plugins) {plugin.apply(compiler);}}//4.执行对象的run方法开始执行编译compiler.run();function babelLoader(source) {return `var sum = function sum(a, b) {return a + b;};`;}
4、webpack.config.js
const path = require("path");const RunPlugin = require("./plugins/RunPlugin");const DonePlugin = require("./plugins/DonePlugin");module.exports = {context: process.cwd(),//当前的根目录mode: "development",//工作模 式 是开发模 式devtool: false,//不生成sourcemapentry: "./src/app.js",//入口文件output: {path: path.resolve(__dirname, "dist"),//输出的路径filename: "main.js",//文件名},module: {rules: [{test: /\.jsx?$/,use: {loader: "babel-loader",options: {presets: ["@babel/preset-env"],},},include: path.join(__dirname, "src"),exclude: /node_modules/,},],},plugins: [new RunPlugin(),new DonePlugin()],devServer: {},};
5、webpack.flow.js
/*** webpack的工作流程*/let { SyncHook } = require("tapable");let fs = require('fs');class Compiler {constructor(options) {this.options = options;this.hooks = {run:new SyncHook(),done:new SyncHook()}}run(){let modules = [];let chunks = [];let files = [];this.hooks.run.call(); //触发run钩子执行//根据配置中的entry找出所有的入口文件let entry = path.join(this.options.context, this.options.entry);//从入口文件出发,调用所有配置的Loader对模块进行编译,再找出该模块依赖的模块,//再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;//1.读取模块内容//let sum = (a, b) => a + b;let title = require("./title");let entryContent = fs.readFileSync(entry, "utf8");let entrySource = babelLoader(entryContent);//模块module chunk代码块 file bundle 文件的关系let entryModule = { id: entry, source: entrySource };modules.push(entryModule);//把入口模块的代码转成抽象语法树AST,分析里面的import 和require依赖let title = path.join(this.options.context, "./src/title.js");let titleContent = fs.readFileSync(title, "utf8");let titleSource = babelLoader(titleContent);//模块module chunk代码块 file bundle 文件的关系let titleModule = { id: title, source: titleSource };modules.push(titleModule);//根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunklet chunk = { name: "main", modules };chunks.push(chunk);//再把每个Chunk转换成一个单独的文件加入到输出列表let file = {file: this.options.output.filename,source: `(function (modules) {function __webpack_require__(moduleId) {var module = { exports: {} };modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);return module.exports;}return __webpack_require__("./src/app.js");})({"./src/app.js": function (module, exports, __webpack_require__) {var title = __webpack_require__("./src/title.js");console.log(title);},"./src/title.js": function (module) {module.exports = "title";},});`,};files.push(file);//在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统let outputPath = path.join(this.options.output.path,this.options.output.filename);fs.writeFileSync(outputPath, file.source, "utf8");//在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,//并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果this.hooks.done.call();}}/*** 1.初始化参数:从配置文件和Shell语句中读取与合并参数,得出最终的参数;*/let options = require('./webpack.config');//开始编译:用上一步得到的参数初始化Compiler对象let compiler = new Compiler(options);//加载所有配置的插件,执行对象的run方法开始执行编译if(options.plugins&& Array.isArray(options.plugins)){for(const plugin of options.plugins){plugin.apply(compiler);}}//确定入口:根据配置中的entry找出所有的入口文件compiler.run();///es6编译成es5function babelLoader(source){return `let sum = function sum(a, b){return a + b;};let title = require('./title');`;}
6、tapable.js
/*** button.addEventListener('click',()=>{console.log('click')});* button.trigger('click');*///let {SyncHook} = require('tapable');class SyncHook {constructor() {this.taps = [];}tap(name, fn) {this.taps.push(fn);}call() {this.taps.forEach((tap) => tap());}}let hook = new SyncHook();//hook.addEventListener();hook.tap('some name', () => {console.log('some name');});hook.call();/*function add(){}add.call(); *///webpack-dev-server memory-fs
7、debug.js
const webpack = require("webpack");const config = require("./webpack.config.js"); //1.读取配置文件debugger;const compiler = webpack(config);function compilerCallback(err, stats) {const statsString = stats.toString();console.log(statsString);}compiler.run((err, stats) => {compilerCallback(err, stats);});
8、插件
plugins/DonePlugin.js
module.exports = class DonePlugin {apply(compiler) {compiler.hooks.done.tap("DonePlugin", () => {console.log("DonePlugin");});}};
plugins/RunPlugin.js
module.exports = class RunPlugin {apply(compiler) {compiler.hooks.run.tap("RunPlugin", () => {console.log("RunPlugin");});}}
9、应用
src/app.js
let sum = (a, b) => a + b;let title = require('./title');
src/title.js
module.exports = 'title';
.gitinore
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.# dependencies/node_modules/.pnp.pnp.js# testing/coverage# production/build# misc.DS_Store.env.local.env.development.local.env.test.local.env.production.localnpm-debug.log*yarn-debug.log*yarn-error.log*
