从零构建webpack

@lucifer

目录

1. webpack是什么

2. 实现一个最基本的webpack

3. 给webpack 添加loader功能

webpack是什么

webpack本质是字符串处理

使用了ast处理`字符串`,而不是正则

一个例子🌰

const { commit, pre, next, showLogs, showAllLogs } = require('./git');

commit({ a: 1 }, '初始化');

commit({ a: 1, b: 2 }, '添加一个');

commit({ a: 2 }, '修改a');

commit({ a: 1 }, '删除b');

pre();
// next();
pre();
pre();
pre();
next();

module.exports = {
    alwaysReturnOne() {
        return 1;
    }
}

如何打包 📦 下面的代码

这里有2个关键点

1. 依赖处理

2.转化为target可以运行的代码

如果不考虑依赖和转化问题呢?

上述问题降级为打包下面的代码

function commit(any, msg) {

}

function goBack(stepCount) {
  return (head = stepCount > head ? 0 : head - stepCount);
}

const pre = () => goBack(1);
const next = () => goBack(-1);
commit({ a: 1 }, '初始化');
commit({ a: 1, b: 2 }, '添加一个');
commit({ a: 2 }, '修改a');
commit({ a: 1 }, '删除b');
pre();
// next();
pre();
pre();
pre();
next();

const code = fs.readFileSync(filename, {
  encoding: "utf-8" // 假设文件是utf-8编码
});

const wrap = new Function(code);

wrap();

我们可以这么处理

如果考虑依赖呢?

这里以commonjs模块化规范讲解

modules with dependencies

我们将一个模块抽象为一个普通的JS对象

{
    dependencies: ['./git.js'],
    id: 0,
    filename: '/Users/lucifer/koro/src/git.test.js',
    code: "通过fs api 读取的文件内容",
    mapping: {
       './git.js': 1
    }
}

可以看出这里git.test.js 依赖 git.js

module with dependency

{
    dependencies: ['./git.js'],
    id: 0,
    filename: '/Users/lucifer/koro/src/git.test.js',
    code: "通过fs api 读取的文件内容",
    mapping: {
       './git.js': 1
    }
};
{
    dependencies: [],
    id: 1,
    filename: '/Users/lucifer/koro/src/git.js',
    code: "通过fs api 读取的文件内容",
    mapping: {
    }
};
[
  {
    id: 0,
    code:
      '"use strict";\n\nvar _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; };\n\nvar say = _interopRequire(require("./say.js"));\n\nvar name = require("./info.js").name;\n\n\nconsole.log(say(name));',
    mapping: { "./say.js": 1, "./info.js": 2 }
  },
  {
    id: 1,
    code:
      '"use strict";\n\nmodule.exports = function (name) {\n  return "hello " + name;\n};',
    mapping: {}
  },
  {
    id: 2,
    code:
      '"use strict";\n\nvar name = require("./name.js").name;\n\n\nmodule.exports = {\n  name: name\n};',
    mapping: { "./name.js": 3 }
  },
  {
    id: 3,
    code: '"use strict";\n\nmodule.exports = {\n  name: "lucifer"\n};',
    mapping: {}
  }
]

modules with dependencies

解决浏览器中没有module,require,exports关键字


    ...
    function require(name) {
      return ...
    }
    const module = { exports: {} };
    const wrap = new Function("require", "module", "exports", code); //   注意这一行
    wrap(require, module, module.exports); // 注意这一行
    ...

bundle-core(解决了依赖问题)

function webpackRequire(id) {
    const { code, mapping } = modules[id];
    function require(name) {
      return webpackRequire(mapping[name]);
    }
    const module = { exports: {} };
    const wrap = new Function("require", "module", "exports", code);
    wrap(require, module, module.exports);
    return module.exports;
}
webpackRequire(0);

转化为target可以运行的代码

这里只考虑浏览器

webpack本身不做转化工作,而是将其交给loader处理,因此我们只需要使用loaders处理即可。这是一种非常好的设计

想打包less,可以使用less-loader

想转移es6+ 语法, 可以使用babel-loader

问题转变为开发一个可以运用loaders到webpack的方法即可

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。 本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。

 

by  @webpack

官方对loader的描述

官方的loader例子

module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.txt$/,
        use: ["raw-loader"]
      }
    ]
  }
};

值得注意的是loaders是链式的

const { compose } = require("./utils");

const applyLoaders = (fullpath, rules, content) => {
  let ret = content;
  let hit = false;
  rules.forEach(rule => {
    if (rule.test.test(fullpath)) {
      hit = true;
      const loaders = rule.use.map(item =>
        item.loader.bind({
          loaders: rule.use
        })
      );

      ret = compose(...loaders)(content);
    }
  });
  if (!hit) {
    console.log(
      `${fullpath}: You may need an appropriate loader to handle this file type.`
    );
  }

  return ret;
};
module.exports = {
  applyLoaders
};
  const code = applyLoaders(absoluteEntryPath, rules, content);

集成loaders的实现

使用

const { bundle } = require("../../src/main");

const babelLoader = require("6to5-loader"); // 这个是babel早期的一个loader

// 去除单行注释
const customLoader = content => content.replace(/\/\/.*\n/g, "");

bundle({
  entry: "../examples/loaders/index.js",
  output: {
    path: "dist",
    filename: "bundle.js"
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: babelLoader
          },
          {
            loader: customLoader
          }
        ]
      }
    ]
  }
});

打包结果-dist.js

(function(modules) {
  function webpackRequire(id) {
    const { code, mapping } = modules[id];
    function require(name) {
      return webpackRequire(mapping[name]);
    }
    const module = { exports: {} };
    const wrap = new Function("require", "module", "exports", code);
    wrap(require, module, module.exports);
    return module.exports;
  }
  webpackRequire(0);
})([
  {
    id: 0,
    code:
      '"use strict";\n\nvar _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; };\n\nvar say = _interopRequire(require("./say.js"));\n\nvar name = require("./info.js").name;\n\n\nconsole.log(say(name));',
    mapping: { "./say.js": 1, "./info.js": 2 }
  },
  {
    id: 1,
    code:
      '"use strict";\n\nmodule.exports = function (name) {\n  return "hello " + name;\n};',
    mapping: {}
  },
  {
    id: 2,
    code:
      '"use strict";\n\nvar name = require("./name.js").name;\n\n\nmodule.exports = {\n  name: name\n};',
    mapping: { "./name.js": 3 }
  },
  {
    id: 3,
    code: '"use strict";\n\nmodule.exports = {\n  name: "lucifer"\n};',
    mapping: {}
  }
]);

项目地址:https://github.com/azl397985856/mono-webpack

creating x from zero series地址:

https://github.com/azl397985856/mono-series

Thanks~

Made with Slides.com