Webpack解析与讲解

一、什么是Webpack?

  一个基于node.js的前端模块化/预处理/扁平化处理器。

二、为什么要使用Webpack?

  1.  解决业务代码中的各种依赖,模块加载,静态文件引入问题(重复依赖/强依赖,阻塞加载,资源整合)
  2.  使浏览器支持众多样式预处理器(sass, less, stylus)
  3.  使浏览器支持众多第三方框架与工具(react/vue/angluar)
  4.  使浏览器支持ECMA5以上的特性和语法

三、Webpack在打包时都做了些什么?

  1. 生成一个包含入口出口路径模块的compiler类
  2. 获取抽象语法树(AST),建立模块之间关系(@babel/parser)
  3. 找出所有依赖模块(@babel/traverse)
  4. AST 转换为 code(@babel/core 和 @babel/preset-env)
  5. 递归解析所有依赖项,生成依赖关系图(mainfest.json)
  6. 重写 require 函数,输出 bundle(即浏览器能够识别的模块)
const fs = require('fs')
const path = require('path')
const options = require('./webpack.config')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const { transformFromAst } = require('@babel/core')

const Parser = {
  getAst: path => {
    // 读取入口文件
    const content = fs.readFileSync(path, 'utf-8')
    // 将文件内容转为AST抽象语法树
    return parser.parse(content, {
      sourceType: 'module'
    })
  },
  getDependecies: (ast, filename) => {
    const dependecies = {}
    // 遍历所有的 import 模块,存入dependecies
    traverse(ast, {
      // 类型为 ImportDeclaration 的 AST 节点 (即为import 语句)
      ImportDeclaration({ node }) {
        const dirname = path.dirname(filename)
        // 保存依赖模块路径,之后生成依赖关系图需要用到
        const filepath = './' + path.join(dirname, node.source.value)
        dependecies[node.source.value] = filepath
      }
    })
    return dependecies
  },
  getCode: ast => {
    // AST转换为code
    const { code } = transformFromAst(ast, null, {
      presets: ['@babel/preset-env']
    })
    return code
  }
}

class Compiler {
  constructor(options) {
    // webpack 配置
    const { entry, output } = options
    // 入口
    this.entry = entry
    // 出口
    this.output = output
    // 模块
    this.modules = []
  }
  // 构建启动
  run() {
    // 解析入口文件
    const info = this.build(this.entry)
    this.modules.push(info)
    this.modules.forEach(({ dependecies }) => {
      // 判断有依赖对象,递归解析所有依赖项
      if (dependecies) {
        for (const dependency in dependecies) {
          this.modules.push(this.build(dependecies[dependency]))
        }
      }
    })
    // 生成依赖关系图
    const dependencyGraph = this.modules.reduce(
      (graph, item) => ({
        ...graph,
        // 使用文件路径作为每个模块的唯一标识符,保存对应模块的依赖对象和文件内容
        [item.filename]: {
          dependecies: item.dependecies,
          code: item.code
        }
      }),
      {}
    )
  }
  build(filename) {
    const { getAst, getDependecies, getCode } = Parser
    const ast = getAst(filename)
    const dependecies = getDependecies(ast, filename)
    const code = getCode(ast)
    return {
      // 文件路径,可以作为每个模块的唯一标识符
      filename,
      // 依赖对象,保存着依赖模块路径
      dependecies,
      // 文件内容
      code
    }
  }
  // 重写 require函数,输出bundle
  generate() {}
}

new Compiler(options).run()

四、主要涉及知识点:

  1. require/import
  2. AMD/CMD
  3. babel与loader

- require与import

  1. require是一个同步动态加载,导出的是当前整个模块(一次导出多次使用)
 const fs = require('fs') // 其中require的路径是支持模版语法的
 console.log(JSON.stringify(fs.readFileSync)
  1. import是一个同步静态加载,导出的是模块引用(即实现部分导出)
import { addSum } from './path'
addSum()
  webpack最终打出的来的包 还是通过babel降维得到的通过amd/cmd规范组合的require包。

- AMD与CMD(异步加载)

  1. AMD(Async Module Definition) 异步模块定义(require.js)
  define(['./a', './b'], function([a,b])) {
    a.doSomething()
    b.doSomething()
  }

  依赖前置、在定义模块的时候就要声明其依赖的模块

  1. CMD(Common Module Definition) 通用模块定义(sea.js)
define(function(require, exports, module) {   
    var a = require('./a')   
    a.doSomething()   
    var b = require('./b')
    b.doSomething()  
})

就近依赖、只有在用到某个模块的时候再去加载

- babel(Babel 是一个 JavaScript 编译器)

由于浏览器的支持速度远远慢于语言的更新速度。所以我们需要一个强大的能够向下兼容的编译器来帮助浏览器识别我们使用的这些新特性。包括:

  1. 语法转换
  2. 通过 Polyfill 方式在目标环境中添加缺失的特性
  3. 源码转换 (codemods)

eg:

 // Babel 输入: ES2015 箭头函数
  [1, 2, 3].map((n) => n + 1);

  // Babel 输出: ES5 语法实现的同等功能
  [1, 2, 3].map(function(n) {
    return n + 1;
  });

我们现在使用到的很多特性/预设/插件/配置/工具等 都是在有babel这个生态中维护(具体可以参考官网 https://www.babeljs.cn/docs/options)

五、要优化Webpack可以怎么做?

分析 :

BundleAnalyzerPlugin

Speed-measure-webpack-plugin

  1. 优化加载静态资源的大小

    (1) 将资源上传至服务器

    (2) 开启gzip

    (3) 压缩静态资源(图片剔除sourcemap, 代码内容格式化)

    (4) 剔除重复的静态资源

  2. 优化加载(同步/异步)依赖模块的多少

    (1) 合理划分同步加载/异步加载模块(包括路由,依赖,以及第三方库等)

    (2) 尽量使用按需加载,减少依赖重复引用

    (3) 剔除不必要的第三方依赖

    (4) 配置跳过一些大的第三方依赖babel-loader

  3. 添加缓存(manfest.json / babel-loader)

    推荐hardSource(webpack 5.0) 与 webpack(4.0) babel-cache

  4. 启用并行构建(thread-loader)
   (1)注意构建时间:(改进前)

  (2)注意构建时间:(改进后)

 

posted @ 2023-02-03 14:08  行走的蒲公英  阅读(122)  评论(0编辑  收藏  举报