webpack专题

一、编译

编译命令

直接cmd下执行webpack 即可,但是配置成为node项目,就可以使用npm的快捷脚本了,
那么我再提供一个在npm下好用的script命令(只支持windows)

 "scripts": {
    "build":"PowerShell.exe rm ./dist/* && webpack"
  },

普通编译

源代码

console.log(123);

编译后代码

(() => {
  eval("console.log(123);\n\n//# sourceURL=webpack://webpack-demo/./src/index.js?");
})();

静态导入编译

源代码

// index.js
import util from './util';
console.log(123);
// util.js
export default{
    say(){
        console.log('hello')
    }
 }

编译后代码

(() => {
  // 所有的模块
  var __webpack_modules__ = ({
    "./src/index.js": ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
      eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./util */ \"./src/util.js\");\n\r\nconsole.log(123);\n\n//# sourceURL=webpack://webpack-demo/./src/index.js?");
    }),

    "./src/util.js": ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
      eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\r\n    say(){\r\n        console.log('hello')\r\n    }\r\n });\n\n//# sourceURL=webpack://webpack-demo/./src/util.js?");
    })
  });

  // 模块缓存
  var __webpack_module_cache__ = {};

  // require 方法定义
  function __webpack_require__(moduleId) {
    if (__webpack_module_cache__[moduleId]) {
      return __webpack_module_cache__[moduleId].exports;
    }
    var module = __webpack_module_cache__[moduleId] = {
      exports: {}
    };
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
    return module.exports;
  }
  (() => {
    __webpack_require__.d = (exports, definition) => {
      for (var key in definition) {
        if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
          Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
        }
      }
    };
  })();
  (() => {
    __webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop)
  })();
  (() => {
    __webpack_require__.r = (exports) => {
      if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
      }
      Object.defineProperty(exports, '__esModule', { value: true });
    };
  })();


  // 入口函数
  __webpack_require__("./src/index.js");
})();

动态导入编译

源代码

// index.js
import('./util');
console.log(123);
// util.js
export default{
    say(){
        console.log('hello')
    }
 }

编译后代码

// main.js webpackBootstrap即启动入口文件
(() => {
  // 模块和模块缓存
  var __webpack_modules__ = ({});
  var __webpack_module_cache__ = {};

  // require 方法定义
  function __webpack_require__(moduleId) {
    if (__webpack_module_cache__[moduleId]) {
      return __webpack_module_cache__[moduleId].exports;
    }
    var module = __webpack_module_cache__[moduleId] = {
      exports: {}
    };
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
    return module.exports;
  }
  __webpack_require__.m = __webpack_modules__;
  (() => {
    __webpack_require__.d = (exports, definition) => {
      for (var key in definition) {
        if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
          Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
        }
      }
    };
  })();
  (() => {
    __webpack_require__.f = {};
    __webpack_require__.e = (chunkId) => {
      return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
        __webpack_require__.f[key](chunkId, promises);
        return promises;
      }, []));
    };
  })();
  (() => {
    __webpack_require__.u = (chunkId) => {
      return "" + chunkId + ".js";
    };
  })();
  (() => {
    __webpack_require__.g = (function () {
      if (typeof globalThis === 'object') return globalThis;
      try {
        return this || new Function('return this')();
      } catch (e) {
        if (typeof window === 'object') return window;
      }
    })();
  })();
  (() => {
    __webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop)
  })();
  (() => {
    var inProgress = {};
    var dataWebpackPrefix = "webpack-demo:";
    __webpack_require__.l = (url, done, key) => {
      if (inProgress[url]) { inProgress[url].push(done); return; }
      var script, needAttach;
      if (key !== undefined) {
        var scripts = document.getElementsByTagName("script");
        for (var i = 0; i < scripts.length; i++) {
          var s = scripts[i];
          if (s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; }
        }
      }
      if (!script) {
        needAttach = true;
        script = document.createElement('script');

        script.charset = 'utf-8';
        script.timeout = 120;
        if (__webpack_require__.nc) {
          script.setAttribute("nonce", __webpack_require__.nc);
        }
        script.setAttribute("data-webpack", dataWebpackPrefix + key);
        script.src = url;
      }
      inProgress[url] = [done];
      var onScriptComplete = (prev, event) => {
        script.onerror = script.onload = null;
        clearTimeout(timeout);
        var doneFns = inProgress[url];
        delete inProgress[url];
        script.parentNode && script.parentNode.removeChild(script);
        doneFns && doneFns.forEach((fn) => fn(event));
        if (prev) return prev(event);
      }
        ;
      var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
      script.onerror = onScriptComplete.bind(null, script.onerror);
      script.onload = onScriptComplete.bind(null, script.onload);
      needAttach && document.head.appendChild(script);
    };
  })();
  (() => {
    __webpack_require__.r = (exports) => {
      if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
      }
      Object.defineProperty(exports, '__esModule', { value: true });
    };
  })();
  (() => {
    var scriptUrl;
    if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + "";
    var document = __webpack_require__.g.document;
    if (!scriptUrl && document) {
      if (document.currentScript)
        scriptUrl = document.currentScript.src
      if (!scriptUrl) {
        var scripts = document.getElementsByTagName("script");
        if (scripts.length) scriptUrl = scripts[scripts.length - 1].src
      }
    }
    if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
    scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");
    __webpack_require__.p = scriptUrl;
  })();
  (() => {
    var installedChunks = {
      "main": 0
    };
    __webpack_require__.f.j = (chunkId, promises) => {
      var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
      if (installedChunkData !== 0) {
        if (installedChunkData) {
          promises.push(installedChunkData[2]);
        } else {
          if (true) {
            var promise = new Promise((resolve, reject) => {
              installedChunkData = installedChunks[chunkId] = [resolve, reject];
            });
            promises.push(installedChunkData[2] = promise);
            var url = __webpack_require__.p + __webpack_require__.u(chunkId);
            var error = new Error();
            var loadingEnded = (event) => {
              if (__webpack_require__.o(installedChunks, chunkId)) {
                installedChunkData = installedChunks[chunkId];
                if (installedChunkData !== 0) installedChunks[chunkId] = undefined;
                if (installedChunkData) {
                  var errorType = event && (event.type === 'load' ? 'missing' : event.type);
                  var realSrc = event && event.target && event.target.src;
                  error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
                  error.name = 'ChunkLoadError';
                  error.type = errorType;
                  error.request = realSrc;
                  installedChunkData[1](error);
                }
              }
            };
            __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId);
          } else installedChunks[chunkId] = 0;
        }
      }
    };



    // 异步加载脚本回调
    var webpackJsonpCallback = (data) => {
      var [chunkIds, moreModules, runtime] = data;
      var moduleId, chunkId, i = 0, resolves = [];
      for (; i < chunkIds.length; i++) {
        chunkId = chunkIds[i];
        if (__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
          resolves.push(installedChunks[chunkId][0]);
        }
        installedChunks[chunkId] = 0;
      }
      for (moduleId in moreModules) {
        if (__webpack_require__.o(moreModules, moduleId)) {
          __webpack_require__.m[moduleId] = moreModules[moduleId];
        }
      }
      if (runtime) runtime(__webpack_require__);
      parentChunkLoadingFunction(data);
      while (resolves.length) {
        resolves.shift()();
      }

    }

    var chunkLoadingGlobal = self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || [];
    var parentChunkLoadingFunction = chunkLoadingGlobal.push.bind(chunkLoadingGlobal);
    chunkLoadingGlobal.push = webpackJsonpCallback;
  })();


  // 入口函数(这个我处理过,方便阅读)
   __webpack_require__.e(/*! import() */ "src_util_js").then(__webpack_require__.bind(__webpack_require__, /*! ./util */ "./src/util.js"));
  console.log(123);
 //# sourceURL=webpack://webpack-demo/./src/index.js?;
})();
// src_util_js.js
(self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push([["src_util_js"], {
  "./src/util.js": ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
    eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\r\n    say(){\r\n        console.log('hello')\r\n    }\r\n });\n\n//# sourceURL=webpack://webpack-demo/./src/util.js?");
  })
}]);

看到没,总的来说玩的花样越多,编译后的代码就越长
推荐阅读 文章一

编译的几个特性

支持多种模块化方案
无论是commonJS和是esm都支持,而且混合使用也没问题

万物皆可打包
本身只支持js,但是通过三方扩展的loader,延伸至万物皆可打包

Code Splitting
支持代码切割,就是把代码分成很多很多块( chunk )。对比gulp
然后实现模块的动态加载。
触发这样的场景有这几种情况:commonJS、import()和require.ensure()。
意义:在以前,为了减少 HTTP 请求,通常地,我们都会把所有的代码都打包成一个单独的 JS 文件。但是,如果这个 JS 文件体积很大的话,那就得不偿失了。这时,我们不妨把所有代码分成一块一块,需要某块代码的时候再去加载它;还可以利用浏览器的缓存,下次用到它的话,直接从缓存中读取。很显然,这种做法可以加快我们网页的加载速度

tree-shaking
即移除 JavaScript 上下文中的未引用代码(dead-code),给一张流传甚广的图,你品

ES6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,这就是tree-shaking的基础。
但是通过require和import()动态引入的,依然无法进行tree-shaking。
定义一个工具 util.js

export const add = (x, y)=> {
    return x + y
}
export const reduce= (x, y)=> {
    return x - y
}


// 或者(上下都行)

export default {
    add(x, y) {
        return x + y
    },
    reduce(x, y) {
        return x - y
    }
}

入口index.js

import {add} from './util';

console.log(add);

编译后的文件

// 可以看到reduce因为没有使用,已经被去除了
(()=>{"use strict";console.log(((o,s)=>o+s))})();

参考文章1文章2

二、多页面入口

有时候一个项目可能会有多个入口,非单一的spa应用,有可能是多个组合成一个
那么这个时候就用到了webpack的多入口了

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const config = {
  mode: 'none',
  entry: { // 多入口
    index: './src/index.js',
    second: './src/second.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
  },
  plugins: []
};

for (const key in config.entry) {
  config.plugins.push(new HtmlWebpackPlugin({ // 多页面
    template: './public/index.html', // 生成页面模板
    filename: key + '.html', //生成页面文件名
    chunks: [key] // 引用的模块编译后的js
  }))
}

module.exports = config;

编译后的会生成如下结构

dist
  --index.html
  --index.js
  --second.html
  --second.js

second.html代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script src="second.js"></script>
</body>
</html>

HtmlWebpackPlugin插件文档可以参考:官方文档某人博客

三、一些不重要的概念

module,chunk 和 bundle 。
其实就是同一份逻辑代码在不同转换场景下的取了三个名字:
我们直接写出来的是 module,
webpack 处理时是 chunk---比如import()、require()动态引入,导致切割的文件名
最后生成浏览器可以直接运行的 bundle()---比如(多)入口文件
如果HtmlWebpackPlugin不指定chunks,那么entry的元素都会被打到html中。

四、自定义loader

webpack只能处理js文件,其他文件无法处理
比如我自定义了配置文件,但是文件的格式不是.js而是 .env
如果直接引入进项目中,直接会报错。提示无法解析
所以需要自定义一个loader,比如起个名字叫 env-loader

我的配置文件 .env

appName=测试app
appVersion=1.0

webpack.config.js增加loader配置

const path = require('path');

module.exports = {
  module: {
    rules: [{ 
      test: /\.env$/, 
      use: {
        loader: path.resolve(__dirname, './env-loader.js'),
        options: {
          name: 'env-loader'
        }
      }
    }]
  }
};

编写该loader env-loader.js

const fs = require('fs');
const path = require('path');
const loaderUtils = require('loader-utils');

// 读取环境变量的文件把它转化成对象
const envCompiler = (data) => { // flie为文件路径
  let d = data.replace(/\r/g, ',').replace(/\n/g, '') // 把换行和回车替换
  let arr = d.split(',').map(item => {
    return item.split('=')
  }) // [ [ 'a', '1' ], [ 'b', '2' ] ]
  let obj = {}
  arr.forEach(item => {
    obj[item[0]] = item[1]
  })
  return obj //{ a: '1', b: '2' }
  // 可以接着处理
  /* 像vue-cli3 新版create-react-app 一样规定环境变量的Key必须以(VUE_APP_)  (REACT_APP_) 开头 */
}


module.exports = function(source) {
    const options = loaderUtils.getOptions(this);
    const result = JSON.stringify(envCompiler(source));
    return `module.exports = ${result}`;
}

使用

import all from './.env';
console.log(all); // {appName:'测试app',appVersion:'1.0'}

五、自定义插件

在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果。远比loader强大
自定义插件 my-plugin.js

const fs = require('fs');
const pluginName = 'MyPlugin';

class MyPlugin {
    constructor(option = {}) {
        this.option = option;
    }
    apply(compiler) {
        // 监听异步compiler的run 钩子
        compiler.hooks.emit.tapAsync(pluginName, (compilation, callBack) => {
            for (const key in compilation.assets) {
                // 干预编译时,产出的代码
                compilation.assets[key]._value = 'var dsh=123;' + compilation.assets[key]._value;
            }
            // 将编译后的文件信息写入
            let result = '';
            for (const key in compilation.assets) {
                result += `${key}=${compilation.assets[key].size()}b \n`;
            }
            console.log(result);
            fs.writeFile('./compiler-file-info.properties', result, { encoding: 'utf8' }, err => { })
            callBack();
        });
    }
}

module.exports = MyPlugin;

使用插件webpack.config.js

const MyPlugin = require('./my-plugin.js')
module.exports = {
  plugins: [
    new MyPlugin({fileType: 'properties'})
  ]
};

看看效果
1、编译后的每个js文件,都被插入了 var dsh=123 这端代码,比如mian.js

var dsh = 123; (() => { var e, t, r, o, ...

2、根目录下生成了个配置文件 compiler-file-info.properties

main.js=3051b 
532.js=154b 
645.js=17050b 
index.html=207b

关于插件中的核心tapable
tapable 是一个类似于 Node.js 中的 EventEmitter的库,但更专注于自定义事件的触发和处理。
webpack 通过 tapable 将实现与流程解耦,所有具体实现通过插件的形式存在。
同样的类库还有facebook家的emitter
tapble这个类库可以直接用在前端

import { SyncHook } from 'tapable';

// 创建一个同步钩子对象
const hook = new SyncHook(['name']);
hook.tap('hello', (name) => {
    console.log(`hello ${name}`);
});
hook.call('ahonn');
import { AsyncSeriesHook } from 'tapable';

// 创建一个异步回调的钩子对象
const hook = new AsyncSeriesHook(['name']);
hook.tapAsync('hello', (name, cb) => {
  setTimeout(() => {
    console.log(`hello ${name}`);
    cb();
  }, 2000);
});

// 2s之后打印
hook.callAsync('ahonn', (name) => {
  console.log('done');
});

六、webpack的未来

唯一不变的是变化。
模块加载在未来大概率会被浏览器原生支持,到那时会不会有新的优化方案
打包这件事,怎么看都像一个补丁,未来的HTTP2会不会彻底使其成为伪需求?
IE8迟早淘汰,Vue/React的市场会无限增大吗,SEO怎么做,Nodejs服务端渲染是答案吗?
这依旧是一个动荡的年代

posted @ 2023-01-12 10:41  丁少华  阅读(40)  评论(0编辑  收藏  举报