模块化 手写实现webpack

发布于:2024-04-25 ⋅ 阅读:(12) ⋅ 点赞:(0)

模块化

common.js 的导入导出方法: require \ export 和 module.exports
export 和 module.export
nodejs 内存1.4G -> 2.8G

cjs

在这里插入图片描述在这里插入图片描述

ESModule

主要区别: require属于动态类型:加载执行 同步
esmodul是静态类型:引入时并不会真的去执行文件 而是先分析依赖关系 再再实例化 最后执行 异步
import 是promise的语法糖 沙箱机制
外链 软链 link
在这里插入图片描述
在这里插入图片描述
python php 解释型语言
js 编译型语言

前端三件事:

  1. 设计:
    webpack的设计:
    文件加载:
    1)从什么地方加载,入口文件,以参数形式写入 ’./index.js‘
    2) 文件加载方法名:getFileInfo(file) - file 文件路径 index.js
    分析方法名 parseFile(Filecontent) - Filecontent 文件内容
    加载完成文件,文件上下文,require,import, 写入dist、bundle.js
    3) 收集依赖
    npm install @babel/traverse
    4)收集完依赖,加载所有⽂件
    内部的文件加载情况,依赖关系
    根据依赖关系加载文件
    - parseModules⽅法:
    - 1. 我们⾸先传⼊主模块路径
    - 2. 将获得的模块信息放到temp数组⾥。
    - 3. 外⾯的循坏遍历temp数组,此时的temp数组只有主模块
    - 4. 循环⾥⾯再获得主模块的依赖deps
    - 5. 遍历deps,通过调⽤getModuleInfo将获得的依赖模块信息push到 temp数组⾥。`
    5) 使得引⼊的代码可以被执⾏最终需要处理require和exports
  2. 注释
  3. 测试

npm init 初始化项目得到package.json 和 package-lock.json文件
npm install 安装依赖
npm install @babel/parser 安装解析器babel
npm install @babel/traverse 收集完依赖,怎么加载所有⽂件
@babel/preset-env es6的代码转成es5的(import es7的语法 浏览器不认识 转成es5)
在这里插入图片描述

引⼊的代码可以被执⾏最终需要处理require和exports

babel里的每个stage代表什么内容: 核心原理是什么
能转义和兼容的范围
低阶段兼容范围更广
高阶段能兼容最新的语法
低阶段包含高阶段

use strict: 严格模式
作用:避免js灵活性造成的问题
用法:文件开头标识 use strict
不能干的事:严格使用js的语法

  1. 全局this
  2. 变量重复定义:为了避免掉js的灵活性造成的问题 避免歧义
  3. eval函数 string变成函数去执行的情况
    在这里插入图片描述

webpack 原生加载器能加载哪些文件: js 和 json
webpack不具备这些功能 通过插件完成

手写实现webpack

webpack的核心概念

1. sourcemap 
2. 文件指纹技术
3. babel 与 AST
4. TreeShaking 
5. 优化
    - 构建速度
    - 提高页面性能
6. 原理
    - webpack
    - plugin
    - loader
7. 核心配置
    - entry: 编译入口,webpack编译的起点
    - Compiler: 编译管理器, webpack启动后会创建compiler对象,知道结束退出
    - compilation: 单次编辑过程的管理器 
    - dependence: 依赖对象 webpack基于该类型记录模块间依赖关系 
    - Module: 内部资源都以module形式存在 所有关于资源的操作,转译,合并都是以module为单位进行的
    - Chunk:百年已完成准备输出是 webpack会将module按照特定的规则组织称一个个chunk 跟最终输出一一对应 
    - Loader: 资源内容转换器 
    - Plugin: webpack构建过程中,会在特定的时机⼴播对应的事件,插件监听这些事件,在特定时间点介⼊编译过程

在这里插入图片描述
在代码里debug
在这里插入图片描述
vscode里debug

  1. 执行完 按语句执行 单步调试:进入到语句里去 单步跳出 刷新 暂停
  2. 代码里写 debugger然后启动
    在这里插入图片描述

手写实现webpack 理解原理完整代码

//  src下创建 add.js⽂件和minus.js⽂件, 在index.js中引⼊,再将index.js⽂件引⼊index.html
// add.js
export default ( a, b ) => {
    return a + b
}
// minus.js
export const minus = ( a, b ) => {
    return a - b
}
// index.js
import add from './add.js'
import { minus } from './minus.js'
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script src="./src/index.js" type="module"></script>
</body>
</html>

// 创建bundle.js文件
// 获取主入口文件
// 解决内部文件循环引用的问题  npm install @babel/parser 解析器
// 收集依赖
// 根据收集的依赖关系 加载所有文件
// 执行加载的文件:浏览器处理不了es67的语法 require、import需要被处理

const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const babel = require('@babel/core')
const getmoduleInfo = (file) => {
    // 读取文件
    const body = fs.readFileSync(file, 'UTF-8')
    const ast = parsesr.parse(body, {
        sourceType: 'module' // 表示我们要解析的是ES模块
    })
    const deps = {} 
    // 
    traverse(ast, {
        // 处理路径
        ImportDeclaration({node}){
            const dirname = path.dirname(file)
            const abspath = "./" + path.join(dirname, node.source.value)
        }
    })
    // 转成ast树
     const { code } = babel.transformFromAst(ast, null,{
         presets: ["@babel/preset-env"]
     })
     const moduleInfo = { file, deps, code}
     return moduleInfo
}

// 解析模块
const parseModules = (file) => {
    // 入口文件
    const entry = getModuleInfo(file)
    const temp = [entry]
    const depsGraph = {}
    for(let i=0;i<temp.length;i++>){
        const deps = temp[i].deps
        if(deps){
            for(const key in deps){
                if(deps.hasOwnProperty(key)){
                    temp.push(getModuleInfo(deps[key]))
                }
            }
        }
    }
    temp.forEach(moduleInfo => {
        depsGraph[moduleInfo.file] = {
            deps: moduleInfo.deps,
            code: moduleInfo.code,
        }
    })
}

// 生成最终bundle文件
const bundle = (file) => {
    const depswGraph = JSON.stringify(parseModules(file))
    return `
        (
            function(graph){
                function require(file) {
                    function absRequire(relPath){
                        return require(graph[file].deps[realPath])
                    }
                    var exports = {}
                    (function (require,exports,code){
                        eval(code)
                    })(absRequire, exportsgraph[file].code)
                    return exports
                }
                require('$file')
            }
        )($depsGraph)
    `
}

const content = bundle('./src/index.js')
// 写入到dist目录下
fs.mkdirSync('./dist')
fs.writeFileSync('./dist/bundle.js',content)

webpack5.0优化

  1. 增加持久化存储能力,提升构建性能(核心)
  2. 提升算法能力来改进长期缓存(降低产物缓存资源的失效率)
  3. 提升treeshaking的能力未降低产物大小和代码生成逻辑
  4. 提升web平台的兼容性能力
  5. 清除了内部结构中在webpack4没有重大更新二引入的一些新特性时留下来的一些奇怪的state
  6. 引入一些重大的变更为未来的一些特性做准备,使得能长期稳定在webpack版本上
    在这里插入图片描述
    如何优化
    在这里插入图片描述

vite

rollup: 编译工具
esbuilder:go语言
为什么vite在开发时用 esbuild, 生产环境用打包 rollup

  1. esbuild不支持es5 不会降级
  2. 不支持render
  3. 不支持split chunk
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    可以把一些包放到npm上
    CICD: shell 的命令

控制并发请求数量

// 并发控制: 10条数据 控制请求的数量不大于三条 
    request(url, maxNum){
      return new Promise(resolve,reject=>{
        if(urls.length){
          resolve([])
          return
        }
        const result= []
        let index = 0 // 下一个请求的下标
        let count = 0 // 当前请求完成的数量
        // 发送请求
        async function request(){
          if(index === urls.length){
            // 当前下标等于最后一个 结束
            return 
          }
          const i = index 
          // 保存序号 使result和urls对应
          const url = urls[index]
          index ++ 
          console.log(url)
          try {
            const resp = await fetch(url)
            result[i]=resp
          } catch (err){
            result[i] = err
          } finally {
            // 判断所有请求是否都完成了
            if(count === urls.length) {
              resolve(result)
            }
            request()
          }
        }
        // maxNum 和 urls。length去最小进行调用
        const times = Math.min(maxNum, urls.length)
        for(let i=0;i<times.length;i++){
          request()
        }
      })
    },
    requestSum(){
    }
  },