AST抽象语法树&webpack逻辑解析

AST抽象语法树是什么?

树形语法结构,会对代码里的函数、变量声明、逻辑操作进行一些校验。

为什么要用AST?

手写了一段代码之后,编译器需要对不同风格的代码按照商定好的规则统一处理,处理成为规则能够顺利执行的语言,才可以发布执行。

编译逻辑:

将代码字符串解析数据单元,再解析成一段token数组,解析时比如方法里还有别的变量,逻辑等,就会形成嵌套,最终就是一个树形结构,然后再生成一个AST树,是一个JSON的数据格式,遍历AST,对内容进行一些变更,生成新的AST再根据它生成一套新的代码。

比如babel 编译一个es6的箭头函数时,会通过编译,输出为一个普通的function,转换成为兼容es6之下的语法。

编译过程总结:

    1. parsing:解析过程,词法分析、语法分析、构建AST;

        1) 词法分析:读取代码字符串,识别每一个单词及其种类,移除空格注释,按照预定规则合并成一个个的标识(token),产出一个token数组(令牌)

        2) 语法分析:读取令牌流,在语法约束下生成树形的中间表示,便于描述逻辑结构,给出了令牌流的结构表示,同时验证语法,如果有错误抛出语法错误

    2. transformation:转化过程,将上一步的解析后的内容按照编译器指定的规则进行处理,形成一个新的表现形式,即改写AST,根据当前AST生成一个新的AST,可以是相同语言或其他语言;

    3. code generation:代码生成,将上一步处理好的内容(AST)转化为新的代码

使用工具查看AST代码: astexplorer.net 网址

用一段代码逻辑表示就是:

function compiler(input){
  let tokens = tokenizer(input); // 生成tokens
  /**
   * 遍历代码字符,匹配到括号、字符、数字、引号等,分类push到tokens数组里 键值对push { type: 'number', value: '123' }
   */
  let ast = parser(tokens); // 生成ast
  /**
   * 遍历type === 'number'时,ast.body就push一条 NumberLiteral 类型的键值对, 如果是方法调用,就会继续遍历调用的params传入
   */
  let newAst = transformer(ast); // 拿到新的ast
  /**
   * 核心功能,新逻辑注入,AST的转换过程
   */
  let output = codeGenerator(nevAst); // 生成新代码
  return output;
}

AST在前端中的应用:

1. 代码优化 eg: 简易版esliint,禁止console

2. 代码检查 代码风格检查,安全检查

3. 代码编辑器和插件 语法高亮 代码提示

webpack篇

webpack5和webpack4的区别

1. 性能优化: Webpack 5引入了许多性能优化,包括持久性缓存、更好的树摇(Tree Shaking)和模块联邦(Module Federation)等特性,以提高构建速度和应用程序性能。

2. 持久性缓存: Webpack 5引入了持久性缓存,通过为构建生成的文件添加哈希值,可以更好地利用浏览器缓存, 从而减少用户下载更新后的代码量。减少了打包的工作量,可以在.cache里取chunk。

3. 模块联邦(Module Federation):这是Webpack 5的一个重要特性,允许多个独立的Webpack构建共享模块从而提高应用程序之间的代码共享和分发。避免重复打包,优化打包时间。

webpack5 在默认情况下,cache 属性值在开发环境中为true,等同于cache:{type:'memory'},而在生产环境下则是被禁用。

对于持久化缓存,则需要指定 cache.type 的属性值为 filesystem,开启这个配置之后,默认会在node_modules 下创建一个.cache 的目录,用来存放生成的缓存文件。

webpack 优化方向:

1. 构建时间优化

2. 打包体积优化

1. 构建时间优化

如何知道webpack编译速度?

 progress-bar-webpack-plugin: 查看编译进度; 安装插件,获取进度条

speed-measure-webpack-plugin: 查看编译速度; 安装插件,获取细粒度的时间

webpack-bundle-analyzer: 打包体积分析(这个插件需要跑起来项目,看到各个部分占据的体积,将较大占用的功能按需引用,可减小体积。)

 npm run eject 可以拿到所有的webpack配置,包含默认配置,安装插件之后,在webpack.config.js中引用,按照文档说明使用插件,在下次build时就能在控制台看到对应的编译数据,这样就可以在优化时有一个比较明确的针对方向。
 

  1) sourceMap 优化

   sourceMap 是将编译后的代码映射回开发时的代码的工具,根据map文件里的变量映射。

   在devtool 的地方修改,可以改变编译map方式。

   关键字: eval、inline、hidden、nosources、cheap

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

      其中:

      inline- | hidden- | eval- : 三个值时三选一

      cheap 可选值,并且可以跟随 module的值;

      nosources: 可选值。

      常用配置推荐

        开发环境推荐使用

        1. eval-cheap-source-map

        2. eval-cheap-module-source-map

        生产环境推荐使用

        1. hidden-source-map

        2. nosources-source-map

  2) watch 优化

      1. 忽略不必要的文件

        可以通过配置watchOptions中的ignore选项,忽略不必要监视的文件或目录,这样可以避免监听一些不相关的文件,减少不必要的构建触发,从而提高性能。

      2. 增加检查延迟

        默认情况下,webpack会立即响应文件的变化并触发重新构建。通过调整watchOptions中的aggregateTimeout选项,增加检查的延迟时间,以减少频繁的构建触发,这样可以在一段时间内收集文件变化,然后一次性触发构建,避免过于频繁的构建。

      3. 使用增量编程

        webpack5中引入了增量编译功能,它只会重新编译发生变化的模块及其依赖,而不是整个项目,这样可以提高构建的速度,特别是在大型项目中。

 3) 缩小loader范围

      配置include、exclude,缩小loader对文件的搜索范围。

      比如配置解析范围在 src目录下的 ts、tsx、js、jsx文件,不包含node_modules

  4) 后缀配置的调整

      按照tsx、ts、jsx、js的顺序匹配,若没匹配到则报错

  5) 并行构建

      将thread-loader放在构建耗时较大的loader之前,会开启多进程进行打包,如果是轻量的loader,有可能会加重负担。

可以使用HappyPack将loader的同步执行转换为并行,同时利用系统资源进行打包

loaders: [{
  test: /\.js$/,
  // 只在src文件夹下进行查找转换 =>限制了转义的文件
  include: [resolve('src')],
  // 不会去查找的路径 => 限制了转义的范围
  exclude: /node modules/,
  // 使用happypack
  loader: "happypack/loader?id=happybabel"
}],
plugins: [{
  new HappyPack({
    id: "happybabel",
    loaders: ['babel-loader?cacheDirectory=true'],
    threads: 4, // 同时开启4个线程
  })
}]
6) DllPlugin 将一部分公共代码抽离出来单独打包
// webpack.dll.conf.js
entry: {
  vendor: ['react']
},
output: {
  path: path.join( dir,'dist'),
  filename: '[name].dll.js',
  library:'[name]-[hash]'
}
plugins: [
  new webpack.DllPlugin({
    name:'[name]-[hash]',
    context:dir,
    path: path.join( dir,'dist', '[name]-manifest.json')
  })
]

// webpack.config.js
plugins: [
  new webpack.DllReferencePlugin({
    context: _dir,
    manifest: require('./dist/vendor-manifest.json')
  })
]
一部分抽离出来单独打包
2. 打包体积的优化
  1) 代码分割

      把不变的第三方库、一些公共模块 比如 util.js 这些单独拆成一个chunk,在访问页面的时候就可以一直使用浏览器中缓存的资源。

  2) tree-shaking

      webpack5 默认开启tree-shaking

  3) 图片质量压缩

      image-webpack-loader 使用 type: 'asset' 选项可以将图片文件或其他资源文件类型视为Asset Module类型,这个类型是webpack5引入的新特性,允许将资源文件直接作为模块导入到代码中,无需手动配置额外的loader。

webpack打包流程?

  1. 初始化: 启动构建,读取与合并配置参数,加载Plugin,实例化Compiler

  2. 编译: 从 entry出发,针对每个 Module 串行调用对应的 loader 去翻译文件的内容,再找到该 Module 依赖的Module,递归地进行编译处理

  3. 输出:将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统中

tree-shaking 实现原理是怎样的?

  1. 识别依赖关系: Tree-shaking 从应用程序的入口点开始,识别所有的依赖关系,包括ES6的import 和 require 语句。

  2. 构建依赖图: 根据识别到的依赖关系,建立一个依赖图。这个图用于表示模块之间的依赖关系和引用关系。

  3. 静态分析:在 Tree-shaking 过程中,不执行实际的代码,而是进行静态分析。这意味着它在编译时期(而不是运行时期)就能够确定哪些代码被引用,哪些代码未被引用。

  4. 标记未使用代码: 通过静态分析,Tree-shaking 标记所有未被引用的代码,包括模块、函数、变量等。

  5. 移除未使用代码: 最后,标记的未使用代码将从最终的打包文件中剔除,以减小文件大小。

实现 Tree-shaking 的前提是,应用程序必须使用 ES6 模块系统,这通常意味着在开发中避免使用CommonJs 的require 语句,因为它们不具备静态特性,无法被Tree-shaking 移除。

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值