虽然使用webpack有一阵日子了,但是对其内部的相关打包过程,还不是很了解,今天翻阅了相关资料后,借鉴写了一个webpack简化版本,仅是其核心的部分,mypack
这里需要node环境(用到了文件读写相关,webpack也是在node上运行的)
// a.js let fs = require('fs') function myRequire (moduleName) { // content指示文件的内容 let content = fs.readFileSync(moduleName, 'UTF8') let fn = new Function('exports', 'module', 'require', '__dirname', '__filename', content + '\n return module.exports') let module = { exports: {} } return fn(module.exports, module, myRequire, __dirname, __filename) } let str = myRequire('./b.js') console.log(str) // b.js module.exports = "Hello RequireJS!"可以直接在浏览器中运行
// myAMD.js let factories = {} function myDefine (moduleName, dependencies, factory) { factory.dependencies = dependencies // 挂在该方法上的依赖关系 factories[moduleName] = factory // 创建对应关系 } function myRequire (mods, callback){ let result = mods.map(mod => { let factory = factories[mod] // 获取该方法 let dependencies = factory.dependencies // 获取该依赖 let exports myRequire(dependencies, function (){ exports = factory.apply(null, arguments) }) return exports }) return callback.apply(null, result) } // 申明依赖 myDefine('name', [], function () { return 'AMD' }) myDefine('age', [], function () { return 666 }) myDefine('person', ['name', 'age'], function (name, age){ let person = 'person ' + name + ' is ' + age return person }) // 引入依赖 第一种方式 myRequire(['name', 'age'], function (name, age){ console.log(name, age) }) myRequire(['person'], function (person){ console.log(person) })然后在mypack.js中文件头部#! /usr/bin/env node 不然会报没有适当的js解析器
之后我们进行链接 npm link 这样我们就全局安装,退回其他位置都可以执行命令 >mypack
首先确定入口文件,出口文件的位置,也即有执行的开始位置,将解析入口文件中的内容,递归的读入,把require替换成本规则可识别的函数名(本例myRequire),并且以key(文件路径),value(内容)的形式放到总的modules中。也即所有用到的依赖已经到内存中。立即执行函数,并且第一个require的就是入口文件,依次执行,也就是碰见myRequire(已经在第二步全部正则替换)时,解析出内容返回。递归的进行myRequire的解析,其参数就为之前的modules的key,通过映射关系取出内容执行。 // mypack.js mypack代码 #! /usr/bin/env node let entry = './src/index.js' // 入口文件 let output = './dist/index.js' // 出口文件 const fs = require('fs') const path = require('path') let modules = [] let script = fs.readFileSync(entry, 'UTF8') // 匹配对应关系 let replaceReg = (script) => { return script.replace(/require\(['"](.+?)["']\)/g, function () { let name = path.join('./src', arguments[1]) let content = fs.readFileSync(name, 'UTF8') // 对于每拿到的content都进行一次替换 content = replaceReg(content) modules.push({name, content}) return `mypack_require('${name}')` }) } script = replaceReg(script) const ejs = require('ejs') let template = ` (function(modules) { // 引入 function mypack_require(moduleId) { var module = { exports: {} }; modules[moduleId].call(module.exports, module, module.exports, mypack_require); return module.exports; } return mypack_require("<%-entry%>"); }) ({ "<%-entry%>": (function(module, exports, mypack_require) { eval(\`<%-script%>\`); }) // index.js的文件 <%for(let i = 0; i < modules.length; i++){ let module = modules[i]%> , "<%-module.name%>": (function(module, exports, mypack_require) { eval(\`<%-module.content%>\`); }) <%}%> }); ` // 最终result为打包后的结果 let result = ejs.render(template, { entry, script, modules }) fs.writeFileSync(output, result) console.log('mypack success') // 目录结构 |-src | |a.js | |c.js | |index.js // index.js打包前的内容 require('./a.js') console.log('hello mypack') // a.js require('./c.js') console.log('hello mypack666') // c.js console.log('=====c.js======') // index.js 打包后的内容 (function(modules) { // 引入 function mypack_require(moduleId) { var module = { exports: {} }; modules[moduleId].call(module.exports, module, module.exports, mypack_require); return module.exports; } return mypack_require("./src/index.js"); }) ({ "./src/index.js": (function(module, exports, mypack_require) { eval(`mypack_require('src\a.js') console.log('hello mypack')`); }) // index.js的文件 , "src\a.js": (function(module, exports, mypack_require) { eval(`console.log('hello mypack666')`); }) }); 添加一个styleLoader,这里以函数的形式进行放入,也可以单独写一个模块引入 loader 其实就是在解析所有的代码时(递归读入代码,即replaceReg之中,对模块内容在进行一些处理)loader 解析不同的文件是根据正则表达式的后缀进行区分的。这里针对于styleLoader中的css代码,第一是进行了压缩成一行,并且替换掉了/r/n解析时的标志,因为innerText接收的是字符串,然而字符串是无法换行的,所以只能压缩成一行,在将原来空行形成的/r/n替换掉。(文末提出一种不压缩的方案) // mypack.js mypack代码加入了styleLoader #! /usr/bin/env node let entry = './src/index.js' // 入口文件 let output = './dist/index.js' // 出口文件 const fs = require('fs') const path = require('path') let modules = [] let script = fs.readFileSync(entry, 'UTF8') //======================styleLoader申明=================== let styleLoader = function (source) { //模拟styleLoader return ` let style = document.createElement('style') style.innerText = ${JSON.stringify(source).replace(/\\r\\n/g, '')} document.head.appendChild(style) ` } //=====================styleLoader申明结束=================== // 匹配对应关系 let replaceReg = (script) => { return script.replace(/require\(['"](.+?)["']\)/g, function () { let name = path.join('./src', arguments[1]) let content = fs.readFileSync(name, 'UTF8') // 对于每拿到的content都进行一次替换 content = replaceReg(content) //======================应用规则===================== if(/\.css$/.test(name)){ content = styleLoader(content) // 处理styleLoader } //=====================应用规则结束=================== modules.push({name, content}) return `mypack_require('${name}')` }) } script = replaceReg(script) const ejs = require('ejs') let template = ` (function(modules) { // 引入 function mypack_require(moduleId) { var module = { exports: {} }; modules[moduleId].call(module.exports, module, module.exports, mypack_require); return module.exports; } return mypack_require("<%-entry%>"); }) ({ "<%-entry%>": (function(module, exports, mypack_require) { eval(\`<%-script%>\`); }) // index.js的文件 <%for(let i = 0; i < modules.length; i++){ let module = modules[i]%> , "<%-module.name%>": (function(module, exports, mypack_require) { eval(\`<%-module.content%>\`); }) <%}%> }); ` // 最终result为打包后的结果 let result = ejs.render(template, { entry, script, modules }) fs.writeFileSync(output, result) console.log('mypack success') // 目录结构 |-src | |a.js | |c.js | |b.css | |index.js // index.js打包前的内容 require('./a.js') require('./b.css') console.log('hello mypack') // a.js require('./c.js') console.log('hello mypack666') // c.js console.log('=====c.js======') //b.css body{ background: hotpink; } // index.js 打包后的内容 (function(modules) { // 引入 function mypack_require(moduleId) { var module = { exports: {} }; modules[moduleId].call(module.exports, module, module.exports, mypack_require); return module.exports; } return mypack_require("./src/index.js"); }) ({ "./src/index.js": (function(module, exports, mypack_require) { eval(`mypack_require('src\a.js') mypack_require('src\b.css') console.log('hello mypack')`); }) // index.js的文件 , "src\c.js": (function(module, exports, mypack_require) { eval(`console.log('=====c.js======')`); }) , "src\a.js": (function(module, exports, mypack_require) { eval(`mypack_require('src\c.js') console.log('hello mypack666')`); }) , "src\b.css": (function(module, exports, mypack_require) { eval(` let style = document.createElement('style') style.innerText = "body{ background: hotpink;}" document.head.appendChild(style) `); }) }); 不压缩css的一种写法 style.innerText = \\\` ${source} \\\` // 代码在构建的时候 输出到index.js变成了 evel(` ...\` backgrounde... \` ... `) // 代码再被执行的时候,执行evel的时候,就以模板字符串` background ...` 呈现出来 // index.js 打包后变为 style.innerText = \` body{ background: hotpink; } \`