Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webpack简易版实现 #11

Open
iloveyou11 opened this issue Apr 20, 2021 · 0 comments
Open

webpack简易版实现 #11

iloveyou11 opened this issue Apr 20, 2021 · 0 comments
Labels

Comments

@iloveyou11
Copy link
Owner

iloveyou11 commented Apr 20, 2021

简单实现commonjs规范

a.js

module.exports = 'hello'

b.js

const fs = require('fs')

function req(moduleName) {
  const content = fs.readFileSync(moduleName, 'utf8')
    // console.log(content);
    // module.exports = 'hello'

    // new Function语法有以下几种:
    // new Function('a', 'b', 'return a + b'); // 基础语法
    // new Function('a,b', 'return a + b'); // 逗号分隔
    // new Function('a , b', 'return a + b'); // 逗号和空格分隔
    // 在以下的语句中,'exports', 'module', 'require', '__dirname', '__filename'都是传入的参数
    const fn = new Function('exports', 'module', 'require', '__dirname', '__filename', content + '\n return module.exports')
    // console.log(fn.toString())
    // function anonymous(exports,module,require,__dirname,__filename) {
    //   module.exports = 'hello'
    //   return module.exports
    // }
    return fn(exports, module, require, __dirname, __filename)
}

const str = req('./a.js')
console.log(str);
// hello

简单实现AMD规范

const fns = {}

/**
 * @description define声明模块
 * @param {*} moduleName 模块名称
 * @param {*} dependencies 模块依赖
 * @param {*} fn 模块执行函数
 */
function define(moduleName, dependencies, fn) {
  fn.dependencies = dependencies //将依赖记到fn上
  fns[moduleName] = fn
}

/**
 * @description 通过require使用模块
 * @param {*} modules 加载的模块
 * @param {*} cb 回调函数
 */
function require(modules, cb) {
  // console.log(modules)
  // [ 'a', 'b', 'c' ]
  // []
  // []
  // [ 'a' ]
  // []

  // console.log(fns)
  // {
  //   a: [Function] { dependencies: [] },
  //   b: [Function] { dependencies: [] },
  //   c: [Function] { dependencies: [ 'a' ] }
  // }

  let results = modules.map(function (mod) {
    let fn = fns[mod]
    let exports

    // 对依赖模块进行递归
    let dependencies = fn.dependencies
    require(dependencies, function () {
      exports = fn.apply(null, arguments)
    })
    return exports
  })
  cb.apply(null, results)
}

开始测试:

// 开始测试
define('a', [], function () {
  return 'a'
})
define('b', [], function () {
  return 'b'
})
define('c', ['a'], function (a) {
  return a.concat('c')
})

require(['a', 'b', 'c'], function (a, b, c) {
  console.log(a, b, c); // a b ac
})

webpack打包后的核心代码

(function (modules) {
  // moduleId就是文件名
  function require(moduleId) {
    const module = {
      exports: {}
    };
    modules[moduleId].call(module.exports, module, module.exports, require);
    return module.exports;
  }
  return require('./src/index.js')
})
  ({
    "./src/index.js": (function (module, exports) {
      eval("console.log('hello');\n\n//# sourceURL=webpack:///./src/index.js?");
    })
  });

// 打印结果
// hello

实现我们自己的wepack打包工具

  1. 新建文件夹mypack,新建bin文件夹,在bin下创建mypack.js,在这个文件实现打包操作
  2. npm init初始化项目,修改package.json中的bin"mypack": "bin/mypack.js",运行npm link将此命令关联到全局环境下
  3. mypack.js中需要加入#! /usr/bin/env node,告诉当前文件在node环境下运行
  4. 编写mypack.js打包核心代码
  5. 在原有项目文件夹,运行mypack命令即可实现最终的打包

接下来在mypack.js中实现我们自己的webpack。

核心思想:
模板如上,采用模板替换的方式,将代码中的entry、output替换为我们自己的,再将eval的内容换为读取的entry文件内容(模板替换可采用ejs模块实现),最后将替换后的内容写入output文件中。

#! /usr/bin/env node

const fs = require('fs')
const ejs = require('ejs')

const entry = './src/index.js'
const output = './dist/main.js'
const script = fs.readFileSync(entry, 'utf8')


let template = `
(function(modules) {
    // moduleId就是文件名
    function require(moduleId) {
        var module = {
            exports: {}
        };
        modules[moduleId].call(module.exports, module, module.exports, require);
        return module.exports;
    }
    return require("<%-entry%>")
})
({
    "<%-entry%>": (function(module, exports) {
        eval(\`<%-script%>\`);
    })
});
`

let result = ejs.render(template, {
    entry,
    script
})

// result为替换后的结果,最终要写到output中
fs.writeFileSync(output, result)
console.log('打包成功!');

继续完善:
如果打包文件中存在require()引入其他模块的情况,需要进行相关处理,首先我们采用webpack打包看一下原始打包结果,以下是基本骨架:

(function(modules) {
    function require(moduleId) {
        var module = {
            exports: {}
        };
        modules[moduleId].call(module.exports, module, module.exports, require);
        return module.exports;
    }
    return require(require.s = "./src/index.js");
})
({
    "./src/index.js": (function(module, exports, __webpack_require__) {
        eval("const result = __webpack_require__(/*! ./a.js */ \"./src/a.js\")\r\nconsole.log(result);\n\n//# sourceURL=webpack:///./src/index.js?");
    }),
    "./src/a.js": (function(module, exports) {
        eval("module.exports = 'hello'\n\n//# sourceURL=webpack:///./src/a.js?");
    })
});

可见,在传入的参数中,不仅传入了入口文件entry,还传入了引入的其他模块。因此我们继续修改mypack.js,让其支持模块引入:

#! /usr/bin/env node

const fs = require('fs')
const path = require('path')
const ejs = require('ejs')

const entry = './src/index.js'
const output = './dist/main.js'
let script = fs.readFileSync(entry, 'utf8')

// 新增部分
let modules = []

// 处理依赖关系
// ?代表非贪婪捕获
// require('./a.js')
script = script.replace(/require\(['"](.+?)['"]\)/g, function() {
    let name = path.join('./src/', arguments[1]) // ./src/a.js
    let content = fs.readFileSync(name, 'utf8')
    modules.push({
        name,
        content
    })
    return `require('${name}')`
})


// 修改template模板,采用ejs的循环模式
let template = `
(function(modules) {
    function require(moduleId) {
        var module = {
            exports: {}
        };
        modules[moduleId].call(module.exports, module, module.exports, require);
        return module.exports;
    }
    return require(require.s = "<%-entry%>");
})
({
    "<%-entry%>": (function(module, exports,require) {
        eval(\`<%-script%>\`);
    }),
    <%for(let i=0;i<modules.length;i++){
        let module=modules[i]%>
        "<%-module.name%>": (function(module, exports) {
            eval(\`<%-module.content%>\`);
        }),
    <%}%>
});
`
let result = ejs.render(template, {
    entry,
    script,
    modules // 注意这里需要传入modules
})

// result为替换后的结果,最终要写到output中
fs.writeFileSync(output, result)
console.log('打包成功!');

再次打包,发现文件通过require引入其他模块成功!

接下来,再来支持require('./index.css')引入css样式文件,我们继续修改mypack.js,如果require文件是css文件,则对其内容进行处理:

// 新增css-loader
// source是文件中的内容
// 新建style标签,放入css文件的内容,将style标签插入到head中
let styleLoader = function(source) {
    return `
        let style=document.createElement('style')
        style.innerText=${JSON.stringify(source).replace(/\\r\\n/g,'')}
        document.head.appendChild(style)
    `
}

script = script.replace(/require\(['"](.+?)['"]\)/g, function() {
    let name = path.join('./src/', arguments[1]) // ./src/a.js
    let content = fs.readFileSync(name, 'utf8')
    
    // 这里我们做一下拦截
    if (/\.css$/.test(name)) {
        content = styleLoader(content)
    }
    
    modules.push({
        name,
        content
    })
    return `require('${name}')`
})

再次打包,发现可以成功引入css文件了!

@iloveyou11 iloveyou11 added the js label Apr 20, 2021
@iloveyou11 iloveyou11 changed the title 前端框架&原理 动手实现 webpack简易版实现 Apr 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant