webpack一点点笔记

193 阅读5分钟

脉络

  1. webpack 内部模块机制 webpack 构建原理 tapable tapable 构建流程管控

  2. 抽象语法树 状态机、正则匹配 AST 代码编译流程 手写一个

  3. bundle、bundleless tree shaking

gulp vs webpack gulp 根据配置项构建 流控制 webpack根据模块引用路径构建依赖

  1. 变量 方法 名称 冲突 ==> 模块(作用域)隔离 ==> function(){} => 闭包
  2. 产生依赖之后,保证模块执行顺序 数组 对webpack模块的执行顺序,就是对依赖树进行深度优先遍历 函数执行的过程就是一个深度优先遍历的过程
// module仓库
(function (modules) {
  /**
   * 1. 按照顺序执行模块
   * 2. 存值
   * 3. 执行
   * 4. 取值
   */
  var installedModules = {}; // 存值
  function require(moduleId) {
    if (installedModules[moduleId]) {
      return installedModules[moduleId];
    }

    var module = installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {} // 当前模块存的值
    };

    modules[moduleId].call(module.exports, module, require);
    module.l = true;
    return module.exports;
  }

  return require(0);
})([
  function b(module, require) {
    // a.js
    // import { name } from 'b.js';
    var name = require(1);

    function getName() {
      console.log(name);
    }

    getName();
  },
  function a(module, require) {
    // b.js
    var name = 'Nolan';
    // export name
    module.exports = name;
  },
]);

webpack config ==>

  1. 初始化配置 内部默认配置 合并用户的配置 ==> webpack config
  2. 初始化 文件读写 分析路径 配置webpack wepback 内部 管理构建流程

不同的阶段 做不同的事情 通知系统 tapable 发出通知

webpack内部 插件 tapable 管理整个webpack的执行流程

  1. 不同的电话 不同的处理逻辑 串行 异步 并行
  2. 不同阶段 执行不同通知
  3. 执行任务 tap => 订阅对应阶段的过程 call => 调用对应阶段的过程 编译 性能和控制流程的优化
const {
    SyncHook,
    SyncBailHook, // return 返回 直接结束
    SyncWaterfallHook, // 流式串形
    SyncLoopHook, // 循环逻辑
    AsyncParalleHook, // 异步并行执行回调
    AsyncParallelBaiHook, // 异步并行执行回调,return返回直接结束
    AsyncSeriesHook, // 异步按顺序执行
    AsyncSeriesBailHook, // 异步按顺序执行 return 返回直接结束
    AsyncSeriesAterfallHook, // 异步按顺序执行 参数传递到下一个
    AsyncHook,
} = require('tapable')  // 25.1k(gzipped 7.1k)

let hooks = new SyncHook();

hooks.tap('WarningLampPlugin'), () => {
  console.log('brake plugin');
});

hooks.tap({
  name: 'warpPlufin',
  // stage: -1 // 控制顺序,正负数表示执行前后次序
}, () => {
  console.log('brake plugin1');
});

hooks.tap({
  name: 'warpPlufin2',
}, () => {
  console.log('brake, plugin12');
});

hook.call();

一、环境差异

  • 开发环境
  1. 需要生成sourcemap文件
  2. 需要打印debug信息
  3. 需要live reload 或者 hot reload的功能
  • 生产环境
  1. 可能需要分离css成单独文件、以便多个文件共享同一个css文件
  2. 需要压缩HTML/CSS/JS代码
  3. 需要压缩图片

二、区分环境

  • --mode 用来设置模块内的 process.env.NODE_ENV
  • --env 用来设置webpack配置文件的函数参数
  • cross-env用来设置node环境的process.env.NODE_ENV
  • dotenv 可以按需加载不同的环境变量文件
  • define-plugin 用来配置在 编译时候 用的 全局变量

Prefetch 使用预先拉取,表示该模块可能会被用到,浏览器在空闲时间下下载该模块 import(/* webpackCunkName: 'xx', webpackPrefetch: true */'xx.js').then() --->

Preload 预加载

  • 此资源肯定会用到,优先较高,需要体检获取,要慎用!

  • preload通常用与本页要用到的关键资源,包括关键js、字体、css文件

  • preload将会把资源的下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度。

  • 在资源上添加预先加载的注释,你指明该模块需要立即被使用

  • 在一个资源的加载优先级被分为五个级别,分别是:

    • Hightest 最高
    • High 高
    • Medium 中等
    • Low 低
    • Lowest 最低
  • 异步、延迟插入的脚本(无论在什么位置)在网络上优先级中是Low

  • module: 就是js的模块化webpack支持commonJS/ES6等模块化规范,简单来说就是你通过import语句引入的代码

  • chunk: chunk是webpack根据功能拆分出来的,包括三种情况

    • 你的项目入口(entry)
    • 通过import()动态引入的代码
    • 通过splitChunks拆分出来的代码
  • bundle:bundle是webpack大饱之后的各个文件,一般就是和chunk是一对一的关系,bundle就是对chunk进行编译压缩打包等处理之后的产出

三、Babel

  • 预设 @babel/preset-env

    .babelrc { "presets": ["@babel/preset-env"], }

    .browserslistrc last 2 Chrome versions

  • polyfill

    • @babel/preset-env 默认只转换新的javascript语法,而不专换新的API,比如Iterator, Generator, Set, Maps, Proxy, Reflect, Symbol, Promise等全局对象,以及一些在全局对象上的方法(比如Object.assign)都不转码。
    • 他会通过全局对象和内置对象的prototype上添加方法来实现的。比如运行环境中不支持Array.prototype.find方法,引入polyfill,我们就可以使用es6方法来编写了,但是缺点就是会造成全局空间污染。
    • V7.4.0版本开始, @babel/polyfill 已经废弃

use: { loader: 'babel-loader', options: { presets: [ ["@babel/preset-env"], { useBuiltIns: 'entry', // true 表示加载全部 corejs: 3, targets: "last 2 Chrome versions" } ] }, }

corejs如果是3,需要 在index.js中引入 import 'core-js/stable'; import 'regenerator-runtime/runtime' 按需引入: useBuiltIns: 'usage'

  • babel-runtime 不污染全局环境,适合开发共用库,但是需要手工引入

  • @babel/plugin-transform-runtime

plugins: [ [ "@babel/plugin-transform-runtime", { corejs: 3, // 如果用到了Promise,会自动引入对应的polyfill helpers: false, // 移除内联的babel helpers 并自动引入babel-runtime/helpers regenerator: true } ] ]

插件先执行预设后执行 插件是从前往后 预设是从后往前

  • sourcemap [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

source-map: 外部 错误代码的准确信息和源代码的错误位置 inline-source-map: 内联(只生成一个) 与上面一致 hideen-source-map: 外部 错误代码的信息,但是没有错误位置,不能追踪到源代码的错误,只能提示构建后代码位置 eval-source-map: 内联(每一个文件都生成,都在evallimain1) 与source-map一致 nosources-source-map: 外部 错误代码信息有,没有任何源代码信息 cheap-source-map: 外部 提示错误信息,只精确到行 cheap-module-source-map: 外部 提示错误信息,只精确到行, module会将loader的source map加入

内联 和 外部的区别: 1.外部生成了文件,内联没有。 2.内联更快

  • 开发环境: 速度快、调试更友好 速度: eval>inline>cheap>... eval-cheap-source-map eval-source-map 调试更友好 source-map cheap-module-source-map cheap-source-map

    结论: eval-source-map

  • 生产环境: 源代码要不要隐藏?调试要不要更友好 内联会让代码提价变大 nosources-source-map 全部隐藏 hidden-source-map 只隐藏源代码 --> source-map/cheap-module-source-map