├── example ├── name.js ├── entry.js └── message.js ├── package.json ├── LICENSE ├── .gitignore ├── bundle.js ├── README.md └── bundler.js /example/name.js: -------------------------------------------------------------------------------- 1 | export const name = 'world'; -------------------------------------------------------------------------------- /example/entry.js: -------------------------------------------------------------------------------- 1 | import message from './message.js'; 2 | 3 | console.log(message); -------------------------------------------------------------------------------- /example/message.js: -------------------------------------------------------------------------------- 1 | import {name} from './name.js'; 2 | 3 | export default `hello ${name}!`; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "own_webpack", 3 | "version": "1.0.0", 4 | "description": "simple webpack", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@babel/core": "^7.1.2", 13 | "@babel/highlight": "^7.0.0", 14 | "@babel/parser": "^7.1.3", 15 | "@babel/preset-env": "^7.1.0", 16 | "@babel/traverse": "^7.1.4" 17 | }, 18 | "dependencies": { 19 | "js-beautify": "^1.6.14" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Cookie Feng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /bundle.js: -------------------------------------------------------------------------------- 1 | 2 | (function (modules) { 3 | // 创建一个require()函数: 它接受一个 模块ID 并在我们之前构建的模块对象查找它. 4 | function require(id) { 5 | const [fn, mapping] = modules[id]; 6 | function localRequire(relativePath) { 7 | // 根据mapping的路径,找到对应的模块id 8 | return require(mapping[relativePath]); 9 | } 10 | const module = { exports: {} }; 11 | // 执行每个模块的代码。 12 | fn(localRequire, module, module.exports); 13 | return module.exports; 14 | } 15 | // 执行入口文件, 16 | require(0); 17 | })({ 18 | 0: [ 19 | function (require, module, exports) { 20 | "use strict"; 21 | 22 | var _message = _interopRequireDefault(require("./message.js")); 23 | 24 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 25 | 26 | console.log(_message.default); 27 | }, 28 | { "./message.js": 1 }, 29 | ], 1: [ 30 | function (require, module, exports) { 31 | "use strict"; 32 | 33 | Object.defineProperty(exports, "__esModule", { 34 | value: true 35 | }); 36 | exports.default = void 0; 37 | 38 | var _name = require("./name.js"); 39 | 40 | var _default = "hello ".concat(_name.name, "!"); 41 | 42 | exports.default = _default; 43 | }, 44 | { "./name.js": 2 }, 45 | ], 2: [ 46 | function (require, module, exports) { 47 | "use strict"; 48 | 49 | Object.defineProperty(exports, "__esModule", { 50 | value: true 51 | }); 52 | exports.name = void 0; 53 | var name = 'world'; 54 | exports.name = name; 55 | }, 56 | {}, 57 | ], 58 | }) 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 什么是webpack 2 | 它是一个模块打包器,也可以引用官网的一幅图解释,我们可以看到webpack,可以分析各个模块的依赖关系,最终打包成我们常见的静态文件,.js 、 .css 、 .jpg 、.png。今天我们先不弄那么复杂,我们就介绍webpack是怎么分析ES6的模块依赖,怎么把ES6的代码转成ES5的。 3 | 4 | ![](https://user-gold-cdn.xitu.io/2019/3/2/1693ed9df7905766?w=2124&h=928&f=png&s=153846) 5 | 6 | ## 实现 7 | 由于ES6转ES5中需要用到babel,所以要用到一下插件 8 | 9 | `npm install @babel/core @babel/parser @babel/traverse @babel/preset-env --save-dev` 10 | ### 需要的文件 11 | 使用webpack肯定少不了原文件,我们会涉及三个需要打包的js文件(`entry.js`、`message.js`、`name.js`) 12 | 13 | ``` 14 | // entry.js 15 | 16 | import message from './message.js'; 17 | console.log(message); 18 | ``` 19 | ``` 20 | // message.js 21 | 22 | import {name} from './name.js'; 23 | export default `hello ${name}!`; 24 | ``` 25 | ``` 26 | // name.js 27 | 28 | export const name = 'world'; 29 | ``` 30 | ``` 31 | //bundler.js 32 | // 读取文件信息,并获得当前js文件的依赖关系 33 | function createAsset(filename) {//代码略} 34 | // 从入口开始分析所有依赖项,形成依赖图,采用广度遍历 35 | function createGraph(entry) {//代码略} 36 | // 根据生成的依赖关系图,生成浏览器可执行文件 37 | function bundle(graph) {//代码略} 38 | ``` 39 | `entry.js` 就是我们的入口文件,文件的依赖关系是,`entry.js`依赖`message.js`,`message.js`依赖`name.js`。 40 | 41 | `bundler.js` 是我们简易版的webpack 42 | 43 | 44 | 目录结构 45 | ``` 46 | - example 47 | - entry.js 48 | - message.js 49 | - name.js 50 | - bundler.js 51 | ``` 52 | 53 | ### 如何分析依赖 54 | webpack分析依赖是从一个入口文件开始分析的,当我们把一个入口的文件路径传入,webpack就会通过这个文件的路径读取文件的信息(读取到的本质其实是字符串),然后把读取到的信息转成AST(抽象语法树),简单点来说呢,就是把一个js文件里面的内容存到某种数据结构里,里面包括了各种信息,**其中就有当前模块依赖了哪些模块**。我们暂时把通过传**文件路径**能返回文件信息的这个函数叫 `createAsset` 。 55 | 56 | ### `createAsset`返回什么 57 | 第一步我们肯定需要先从 `entry.js` 开始分析,于是就有了如下的代码,我们先不关心`createAsset`具体代码是怎么实现的,具体代码我会放在最后。 58 | ``` 59 | createAsset("./example/entry.js"); 60 | ``` 61 | 当执行这句代码,`createAsset` 会返回下面的数据结构,这里包括了**模块的id**,**文件路径**,**依赖数组**(`entry.js`依赖了`message.js`,所以会返回依赖的文件名),**code**(这个就是`entry.js` ES6转ES5的代码) 62 | ![](https://user-gold-cdn.xitu.io/2019/3/2/1693eee846b82ac0?w=1482&h=560&f=png&s=101080) 63 | 通过 `createAsset` 我们成功拿到了`entry.js`的依赖,就是 `dependencies` 数组。 64 | 65 | ### `createGraph`返回什么,如何找下一个依赖 66 | 我们通过上面可以拿到entry.js依赖的模块,于是我们就可以接着去遍历`dependencies` 数组,循环调用`createAsset`这样就可以得到全部模块相互依赖的信息。想得到全部依赖信息需要调用 `createGraph` 这个一个函数,它会进行广度遍历,最终返回下面的数据 67 | 68 | ![](https://user-images.githubusercontent.com/15076030/53696618-e8055b80-3e03-11e9-82b6-83b81f5cd82a.png) 69 | 70 | 我们可以看到返回的数据,字段之前都和大家解释了,除了 `mapping`,`mapping`这个字段是把当前模块依赖的**文件名称** 和 模块的id 做一个映射,目的是为了更方便查找模块。 71 | 72 | ### `bundle`返回什么 && 最后步骤 73 | 我们现在已经能拿到每个模块之前的依赖关系,我们再通过调用`bundle`函数,我们就能构造出最后的`bundle.js`,输出如下图 74 | 75 | ![](https://user-images.githubusercontent.com/15076030/53690721-1efb5300-3dab-11e9-9f9b-b4ef4c0691da.png) 76 | 77 | 78 | ### 源码 79 | [点击查看源码](https://github.com/dykily/simple_webpack/blob/master/bundler.js) 80 | ## 最后 81 | 文章可能有不足的地方,请大家见谅,如果有什么疑问可以下方留言讨论。 82 | 83 | 如果大家对文字描述还是不太清楚,建议看我下方提供的视频,我就是从视频中学的,这个是在youtube上的视频,大家懂的,有条件的还是建议看一下。😝 84 | 85 | [官方40分钟教你写webpack](https://www.youtube.com/watch?v=Gc9-7PBqOC8&list=LLHK1mTHpwrUeYgF5gu-Kd4g) 86 | -------------------------------------------------------------------------------- /bundler.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const babylon = require("@babel/parser"); 4 | const traverse = require("@babel/traverse").default; 5 | const babel = require("@babel/core"); 6 | 7 | 8 | let ID = 0; 9 | //读取文件信息,并获得当前js文件的依赖关系 10 | function createAsset(filename) { 11 | //获取文件,返回值是字符串 12 | const content = fs.readFileSync(filename, "utf-8"); 13 | 14 | //讲字符串为ast(抽象语法树, 这个是编译原理的知识,说得简单一点就是,可以把js文件里的代码抽象成一个对象,代码的信息会存在对象中) 15 | //babylon 这个工具是是负责解析字符串并生产ast。 16 | const ast = babylon.parse(content, { 17 | sourceType: "module" 18 | }); 19 | 20 | //用来存储 文件所依赖的模块,简单来说就是,当前js文件 import 了哪些文件,都会保存在这个数组里 21 | const dependencies = []; 22 | 23 | //遍历当前ast(抽象语法树) 24 | traverse(ast, { 25 | //找到有 import语法 的对应节点 26 | ImportDeclaration: ({ node }) => { 27 | //把当前依赖的模块加入到数组中,其实这存的是字符串, 28 | //例如 如果当前js文件 有一句 import message from './message.js', 29 | //'./message.js' === node.source.value 30 | dependencies.push(node.source.value); 31 | } 32 | }); 33 | 34 | //模块的id 从0开始, 相当一个js文件 可以看成一个模块 35 | const id = ID++; 36 | 37 | //这边主要把ES6 的代码转成 ES5 38 | const { code } = babel.transformFromAstSync(ast, null, { 39 | presets: ["@babel/preset-env"] 40 | }); 41 | 42 | return { 43 | id, 44 | filename, 45 | dependencies, 46 | code 47 | }; 48 | } 49 | 50 | //从入口开始分析所有依赖项,形成依赖图,采用广度遍历 51 | function createGraph(entry) { 52 | const mainAsset = createAsset(entry); 53 | 54 | //既然要广度遍历肯定要有一个队列,第一个元素肯定是 从 "./example/entry.js" 返回的信息 55 | const queue = [mainAsset]; 56 | 57 | 58 | for (const asset of queue) { 59 | const dirname = path.dirname(asset.filename); 60 | 61 | //新增一个属性来保存子依赖项的数据 62 | //保存类似 这样的数据结构 ---> {"./message.js" : 1} 63 | asset.mapping = {}; 64 | 65 | asset.dependencies.forEach(relativePath => { 66 | const absolutePath = path.join(dirname, relativePath); 67 | 68 | //获得子依赖(子模块)的依赖项、代码、模块id,文件名 69 | const child = createAsset(absolutePath); 70 | 71 | //给子依赖项赋值, 72 | asset.mapping[relativePath] = child.id; 73 | 74 | //将子依赖也加入队列中,广度遍历 75 | queue.push(child); 76 | }); 77 | } 78 | return queue; 79 | } 80 | 81 | //根据生成的依赖关系图,生成对应环境能执行的代码,目前是生产浏览器可以执行的 82 | function bundle(graph) { 83 | let modules = ""; 84 | 85 | //循环依赖关系,并把每个模块中的代码存在function作用域里 86 | graph.forEach(mod => { 87 | modules += `${mod.id}:[ 88 | function (require, module, exports){ 89 | ${mod.code} 90 | }, 91 | ${JSON.stringify(mod.mapping)}, 92 | ],`; 93 | }); 94 | 95 | //require, module, exports 是 cjs的标准不能再浏览器中直接使用,所以这里模拟cjs模块加载,执行,导出操作。 96 | const result = ` 97 | (function(modules){ 98 | //创建require函数, 它接受一个模块ID(这个模块id是数字0,1,2) ,它会在我们上面定义 modules 中找到对应是模块. 99 | function require(id){ 100 | const [fn, mapping] = modules[id]; 101 | function localRequire(relativePath){ 102 | //根据模块的路径在mapping中找到对应的模块id 103 | return require(mapping[relativePath]); 104 | } 105 | const module = {exports:{}}; 106 | //执行每个模块的代码。 107 | fn(localRequire,module,module.exports); 108 | return module.exports; 109 | } 110 | //执行入口文件, 111 | require(0); 112 | })({${modules}}) 113 | `; 114 | 115 | return result; 116 | } 117 | 118 | const graph = createGraph("./example/entry.js"); 119 | const ret = bundle(graph); 120 | 121 | // 打包生成文件 122 | fs.writeFileSync("./bundle.js", ret); 123 | --------------------------------------------------------------------------------