webpack | 2020-05-15 10:59:25 621次 2次
一、准备
1、初始化 package.json
{ "name": "supack", "version": "1.0.0", "main": "index.js", "license": "MIT", "bin": { // bin的作用是 可以直接运行的命令 比如 wex "supack": "./bin/supack.js" } }
2、创建目录
supack--根目录 bin -- 入口目录 supack.js --入口 lib -- 业务逻辑目录 compiler.js -- 编译主逻辑 temp.ejs -- 生成目标文件(编译后文件)
3、运行
将 supack 丢到待编译项目的 node_modules目录下,然后执行 npx supack 命令执行。
更好的一个方式是将 supack 目录单独出来,通过 npm link 关联到全局,然后在开发项目中通过 npm link supack 关联上 supack,这样在待编译项目中的 node_module 中会被创建一份 supack 出来,而且抽离出来的 supack 修改后,待编译项目中 node_module 下的 supack 也会随着修改,这样做的好处是可以关联多个项目。
执行 npx 的时候,会去找待编译项目中 node_module / .bin目录,如果对应的包不存在的话,就会自动下载,并创建.cmd文件,然后自动找到刚创建的包里面对应模块的bin目录,并执行入口文件:
@ECHO off SETLOCAL CALL :find_dp0 IF EXIST "%dp0%\node.exe" ( SET "_prog=%dp0%\node.exe" ) ELSE ( SET "_prog=node" SET PATHEXT=%PATHEXT:;.JS;=;% ) "%_prog%" "%dp0%\..\supack\bin\supack.js" %* ENDLOCAL EXIT /b %errorlevel% :find_dp0 SET dp0=%~dp0 EXIT /b
二、编写
1、supack.js
#! /usr/bin/env node //打印 版本信息或者其他帮助命令 function run (argv) { if (argv[0] === '-v' || argv[0] === '--version') { console.log(' version is 0.0.1'); } else if (argv[0] === '-h' || argv[0] === '--help') { console.log(' usage:\n'); } } run(process.argv.slice(2)); //get webpack.config.js let path = require('path'); let Compiler = require("../lib/compiler"); //config 配置文件 let config = require(path.resolve('webpack.config.js')); console.log('11111111111111112222222222222') let compiler = new Compiler(config); //执行钩子 compiler.hooks.entryOption.call() compiler.run()
2、compiler.js
let fs = require('fs'); let path = require('path'); //babylon 源码 ---> ast //@babel/traverse 遍历节点 //@babel/types 替换节点 //@babel/generator 替换后的结果生成 let babylon = require('babylon'); let traverse = require('@babel/traverse').default; let generator = require('@babel/generator').default; let t = require('@babel/types'); let ejs = require('ejs'); let { SyncHook } = require('tapable'); class Compiler{ constructor(config) { this.config = config; //'./src/index.js' this.entryId; this.modules = {}; // 入口文件 this.entry = config.entry; //工作路径 this.root = process.cwd(); //钩子 this.hooks = { entryOption: new SyncHook(), compile: new SyncHook(), afterCompile: new SyncHook(), afterPulgins: new SyncHook(), run: new SyncHook(), emit: new SyncHook(), done: new SyncHook(), } //如果传递了 插件 参数 let plugins = this.config.plugins; if(Array.isArray(plugins)){ plugins.forEach(item => { //插件中的方法 不是改变指向 item.apply(this) }) } this.hooks.afterPulgins.call() } getSource(modulePath){ let content = fs.readFileSync(modulePath, 'utf-8'); //加载loader let rules = this.config.module.rules; for(let i = 0, len = rules.length; i < len; i++){ let rule = rules[i], { test, use } = rule, len = use.length - 1; //匹配指定文件 if(test.test(modulePath)){ //获取对应loader函数 function normalLoader(){ let loader = require(use[len--]); content = loader(content) if(len >= 0){ normalLoader() } } normalLoader() } } return content; } //解析源码 AST解析语法树 parse(source, parentPath){ let ast = babylon.parse(source); let dependencies = []; traverse(ast, { CallExpression(p){ let node = p.node; //对应节点 if(node.callee.name == "require"){ node.callee.name = '__webpack_require__'; let moduleName = node.arguments[0].value; moduleName = moduleName + (path.extname(moduleName) ? "" : ".js"); moduleName = './' + path.join(parentPath, moduleName); dependencies.push(moduleName); node.arguments = [t.stringLiteral(moduleName)]; } } }) let sourceCode = generator(ast).code; return { sourceCode, dependencies } } //构建模块 buildModule(modulePath, isEntry){ //get 模块内容 Id let source = this.getSource(modulePath) let moduleName = './' + path.relative(this.root, modulePath); //保存入口名字 if(isEntry){ this.entryId = moduleName; } //创建依赖列表 let {sourceCode, dependencies} = this.parse(source, path.dirname(moduleName)); this.modules[moduleName] = sourceCode; //父模块之外的模块 递归加载 dependencies.forEach(item => { this.buildModule(path.join(this.root, item), false) }) } emitFile(){ //输出路径 let main = path.join(this.config.output.path, this.config.output.filename); //模板 let templateStr = this.getSource(path.join(__dirname, 'main.ejs')); let code = ejs.render(templateStr, { entryId: this.entryId, modules: this.modules }) this.assets = {} this.assets[main] = code fs.writeFileSync(main, this.assets[main]) } //执行 run(){ this.hooks.run.call() this.hooks.compile.call() // 创建模块依赖关系 this.buildModule(path.resolve(this.root, this.entry), true); this.hooks.afterCompile.call() //发送打包后的文件 this.emitFile(); this.hooks.emit.call() this.hooks.done.call() } } module.exports = Compiler;
3、temp.ejs
(function(modules) { var installedModules = {}; function __webpack_require__(moduleId) { if(installedModules[moduleId]) { return installedModules[moduleId].exports; } var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); module.l = true; return module.exports; } return __webpack_require__(__webpack_require__.s = "<%-entryId%>"); })({ <%for(let key in modules){%> "<%-key%>":(function(module, exports, __webpack_require__){ eval(`<%-modules[key]%>`); }), <%}%> });
2人赞