webpack

需要webpack

去搞清楚webpack做了什么之前,我觉得首先要思考一下我们为什么需要webpack,它究竟解决了什么痛点。想想我们日常搬砖的场景:
1.开发的时候需要一个开发环境,要是我们修改一下代码保存之后浏览器就自动展现最新的代码那就好了(热更新服务)
2.本地写代码的时候,要是调后端的接口不跨域就好了(代理服务)
3.为了跟上时代,要是能用上什么ES678N等等新东西就好了(翻译服务)
4.项目要上线了,要是能一键压缩代码啊图片什么的就好了(压缩打包服务)
5.我们平时的静态资源都是放到CDN上的,要是能自动帮我把这些搞好的静态资源怼到CDN去就好了(自动上传服务)

  • 如果与输入相关的需求,找entry(比如多页面就有多个入口)
  • 如果与输出相关的需求,找output(比如你需要定义输出文件的路径、名字等等)
  • 如果与模块寻址相关的需求,找resolve(比如定义别名alias)
  • 如果与转译相关的需求,找loader(比如处理sass处理es678N)
  • 如果与构建流程相关的需求,找plugin(比如我需要在打包完成后,将打包好的文件复制到某个目录,然后提交到git上)

webpack打包出来的什么

webpack搞了很多东西,但最终产出的无非就是经过重重服务处理过的代码,那么这些代码是怎样的呢?
首先我们先来看看入口文件index.js:

console.log('index')
const one = require('./module/one.js')
const two = require('./module/two.js')
one()
two()

嗯,很简单,没什么特别,引入了两个模块,最后执行了它们一下。其中one.js和two.js的代码也很简单,就是导出了个函数:

// one.js
module.exports = function () {
  console.log('one')
}
// two.js
module.exports = function () {
  console.log('two')
}

好了,就是这么简单的代码,放到webpack打包出来的是什么呢?

/******/ (function(modules) { // webpackBootstrap
/******/     // The module cache
/******/     var installedModules = {};
/******/
/******/     // The require function
/******/     function __webpack_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, __webpack_require__);
/******/
/******/         // Flag the module as loaded
/******/         module.l = true;
/******/
/******/         // Return the exports of the module
/******/         return module.exports;
/******/     }
/******/
/******/
/******/     // expose the modules object (__webpack_modules__)
/******/     __webpack_require__.m = modules;
/******/
/******/     // expose the module cache
/******/     __webpack_require__.c = installedModules;
/******/
/******/     // define getter function for harmony exports
/******/     __webpack_require__.d = function(exports, name, getter) {
/******/         if(!__webpack_require__.o(exports, name)) {
/******/             Object.defineProperty(exports, name, {
/******/                 configurable: false,
/******/                 enumerable: true,
/******/                 get: getter
/******/             });
/******/         }
/******/     };
/******/
/******/     // getDefaultExport function for compatibility with non-harmony modules
/******/     __webpack_require__.n = function(module) {
/******/         var getter = module && module.__esModule ?
/******/             function getDefault() { return module['default']; } :
/******/             function getModuleExports() { return module; };
/******/         __webpack_require__.d(getter, 'a', getter);
/******/         return getter;
/******/     };
/******/
/******/     // Object.prototype.hasOwnProperty.call
/******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/     // __webpack_public_path__
/******/     __webpack_require__.p = "";
/******/
/******/     // Load entry module and return exports
/******/     return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

console.log('index')
const one = __webpack_require__(1)
const two = __webpack_require__(2)
one()
two()


/***/ }),
/* 1 */
/***/ (function(module, exports) {

module.exports = function () {
  console.log('one')
}

/***/ }),
/* 2 */
/***/ (function(module, exports) {

module.exports = function () {
  console.log('two')
}

/***/ })
/******/ ]);

简化webpack打包出来的代码

其实进过简化后就可以看到,这些代码意图十分明显,也是我们十分熟悉的套路。

(function (modules) {
    const require = function (moduleId) {
      const module = {}
      module.exports = null
      modules[moduleId].call(module, module, require)
      return module.exports
    }
    require(0)
})([
function (module, require) {
    console.log('index')
    const one = require(1)
    const two = require(2)
    one()
    two()
},
function (module, require) {
    module.exports = function () {
        console.log('one')
    }
},
function (module, require) {
    module.exports = function () {


1.观察一下,我们需要一个自执行函数,这里面需要控制的是这个自执行函数的传参,就是那个数组
2.这个数组是毋容置疑是根据依赖关系来形成的
3.我们要找到所有的require然后将require的路径替换成对应数组的索引
4.将这个处理好的文件输出出来
ok,上代码:

const fs = require('fs')
const path = require('path')
const esprima = require('esprima')
const estraverse = require('estraverse')
// 定义上下文 即所有的寻址都按照这个基准进行
const context = path.resolve(__dirname, '../')
// 处理路径
const pathResolve = (data) => path.resolve(context, data)
// 定义全局数据格式
const dataInfo = {
    // 入口文件源码
    source: '',
    // 分析入口文件源码得出的依赖信息
    requireInfo: null,
    // 根据依赖信息得出的各个模块
    modules: null
}
/**
 * 读取文件
 * @param {String} path 
 */
const readFile = (path) => {
    return new Promise((resolve, reject) => {
        fs.readFile(path, function (err, data) {
            if (err) {
                console.log(err)
                reject(err)
                return
            }
            resolve(data)
        })
    })
}
/**
 * 分析入口源码
 */
const getRequireInfo = () => {
    // 各个依赖的id 从1开始是因为0是入口文件
    let id = 1
    const ret = []
    // 使用esprima将入口源码解析成ast
    const ast = esprima.parse(dataInfo.source, {range: true})
    // 使用estraverse遍历ast
    estraverse.traverse(ast, {
        enter (node) {
            // 筛选出require节点
            if (node.type === 'CallExpression' && node.callee.name === 'require' && node.callee.type === 'Identifier') {
                // require路径,如require('./index.js'),则requirePath = './index.js'
                const requirePath = node.arguments[0]
                // 将require路径转为绝对路径
                const requirePathValue = pathResolve(requirePath.value)
                // 如require('./index.js')中'./index.js'在源码的位置
                const requirePathRange = requirePath.range
                ret.push({requirePathValue, requirePathRange, id})
                id++
            } 
        }
    })
    return ret
}
/**
 * 模块模板
 * @param {String} content 
 */
const moduleTemplate = (content) => `function (module, require) {\n${content}\n},`
/**
 * 获取模块信息
 */
const getModules = async () => {
    const requireInfo = dataInfo.requireInfo
    const modules = []
    for (let i = 0, len = requireInfo.length; i < len; i++) {
        const file = await readFile(requireInfo[i].requirePathValue)
        const content = moduleTemplate(file.toString())
        modules.push(content)
    }
    return modules
}
/**
 * 将入口文件如require('./module/one.js')等对应成require(1)模块id
 */
const replace = () => {
    const requireInfo = dataInfo.requireInfo
    // 需要倒序处理,因为比如第一个require('./module/one.js')中的路径是在源码字符串42-59这个区间
    // 而第二个require('./module/two.js')中的路径是在源码字符串82-99这个区间,那么如果先替换位置较前的代码
    // 则此时源码字符串已经少了一截(从'./module/one.js'变成1),那第二个require的位置就不对了
    const sortRequireInfo = requireInfo.sort((item1, item2) => item1.requirePathRange[0] < item2.requirePathRange[0])
    sortRequireInfo.forEach(({requirePathRange, id}) => {
        const start = requirePathRange[0]
        const end = requirePathRange[1]
        const headerS = dataInfo.source.substr(0, start)
        const endS = dataInfo.source.substr(end)
        dataInfo.source = `${headerS}${id}${endS}`
    })
}
/**
 * 输出打包好的文件
 */
const output = async () => {
    const data = await readFile(pathResolve('./template/indexTemplate.js'))
    const indexModule = moduleTemplate(dataInfo.source)
    const allModules = [indexModule, ...dataInfo.modules].join('')
    const result = `${data.toString()}([\n${allModules}\n])`
    fs.writeFile(pathResolve('./build/output.js'), result, function (err) {
        if (err) {
            throw err;
        }
    })
}
const main = async () => {
    // 读取入口文件
    const data = await readFile(pathResolve('./index.js'))
    dataInfo.source = data.toString()
    // 获取依赖信息
    dataInfo.requireInfo = getRequireInfo()
    // 获取模块信息
    dataInfo.modules = await getModules()                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
    // 将入口文件如require('./module/one.js')等对应成require(1)模块id
    replace()
    // 输出打包好的文件
    output()
    console.log(JSON.stringify(dataInfo))
}
main()

posted @ 2018-08-13 10:32  小二的黑豆  阅读(185)  评论(0编辑  收藏  举报