Giter VIP home page Giter VIP logo

recording's Introduction

Hi there 👋

recording's People

Contributors

xlkang avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar

recording's Issues

01. 在Ipad上体验vscode在线开发

前言

  最近入手一台ipad,打算尝试一下vscode推出的在线开发服务。目前发布的vscode环境有微软官方的Visual Studio CodespacesGitHub Codespaces,coding的cloud studio等,官方和cloud studio价格都不太美丽,GitHub Codespaces还在内测,也不太理想。

  后来发现了开源的code-server,它也是专门为浏览器设计优化的 VS Code,可以自行部署在自己的服务器上面,并且在任意浏览器中访问,另外ipadOS上还有一款原生的VSApp用来代替浏览器连接code-server,它还专门进行了优化,相比浏览器中访问体验顺滑得多,因此决定用code-server来搭建在线开发服务。

准备

  • ipad一台

  • 服务器一台,我用的阿里云ECS 2v4g,测试用只买了一周,系统是Ubuntu v18.04

  • 可以连接到服务器的设备或者终端

服务器部署code-server

  • 下载code-server gzip包(linux, 3.1.1) 下载地址,这里说一句,刚开始直接在服务器上用curl -o命令下载下来,解压报错,原因是这个链接指向的其实不是gzip文件,而是下载链接,于是在github手动下载gzip包并且上传到服务器。

  • 上传,这里我在公司的mac上用的FileZilla,登录远程服务器,上传gzip包到/root目录下

  • 解压到当前目录并进入文件夹

    tar -xf FILE_NAME
    cd FILE_NAME
  • 在服务器上面设置名为 PASSWORD 的环境变量,后面 code-server 启动后就会直接读取这一环境变量并将之作为你的登录密码

  export PASSWORD="YOUR_CODE_SERVER_PASSWORD"
  • 目录中code-server是可执行文件,可以直接在linux下执行,接下来在服务器上启动code-server

    ./code-server --host "0.0.0.0"

    code-server 进程就在远程服务器上启动并开始监听 8080 端口了,同时使用自定义的密码(YOUR_CODE_SERVER_PASSWORD)作为认证密码。

  • 最后打开这个端口的防火墙,在阿里云ECS服务器实例控制台中,在本实例安全组中添加开放8080端口的入网授权策略(允许外网访问8080端口),因为我的code-server服务可能会被不同的ip访问和使用,因此把授权对象设置为0.0.0.0,即允许任何源访问。

  至此服务器上的code-server实例就配置好了,非常方便,然后就可以在任意浏览器中打开{服务器IP}:{8080端口}访问私有的在线版的VS code了。

VSApp优化pad体验

  配置好服务器上的code-server实例后,在ipad上也可以用普通浏览器访问,但是浏览器的导航栏和虚拟键盘,还是很影响使用体验。为了解决这个问题,再使用VSApp替代浏览器去连接code-server。

  • 直接在appStore下载VSApp

  • 打开后选择Self Hosted Server,填写code-server的url和自定义密码,服务器的登录信息等

  • 点击保存

  VSApp 就可以连接我们自己的 code-server 实例,并且自动登录,由于 VSApp 是一个独立的 iOS 应用,并专门为 code-server 进行了优化,因此体验会比浏览器顺滑得多。

总结

  完成以上配置后,打开VSApp体验一下在线编程,很顺滑,打开VS code的终端,可以操作服务器上的所有文件,也像pc上一样支持各种extensions。接下来在服务器上装上gitnodenvmyarn等日常环境,在pad上进行一些日常web项目的coding和一些练习就没有问题了,同时也有了在不同终端中一致的开发环境。

参考文章

为 iPad 部署基于 VS Code 的远程开发环境

05. React Fiber

1 问题

16 之前的版本比对更新 VirtualDOM 的过程是采用循环加递归实现的(Stack 算法),这种比对方式有一个问题,就是一旦任务开始进行就无法中断,如果应用中组件数量庞大,主线程被长期占用,直到整棵 VirtualDOM 树比对更新完成之后主线程才能被释放,主线程才能执行其他任务。这就会导致一些用户交互,动画等任务无法立即得到执行,页面就会产生卡顿,非常影响体验。

核心问题:

  • 递归无法中断,执行重任务耗时长。
  • js又是单线程,无法同时执行其他任务,导致任务延迟页面卡顿。

2 解决方案

  1. 利用浏览器空闲时间执行任务(requestIdleCallback),拒绝长时间占用主线程 🔥 核心
  2. 放弃递归只用循环,因为循环可以被中断
  3. 任务拆分,将任务拆分成一个个的小任务

3 实现思路

在 Fiber 方案中,为了实现任务的终止再继续,DOM比对算法被分成了两部分:

  1. 构建 Fiber (可中断)createElement返回virtualDOM,遍历,为每一个virtualDOM对象构建对应的 Fiber 对象
  2. 提交 Commit (不可中断)根据effectTag 来 commit 对应的操作(新增,删除,修改等)

DOM 初始渲染: virtualDOM -> Fiber -> Fiber[] -> DOM

DOM 更新操作: newFiber vs oldFiber -> Fiber[] -> DOM

4 Fiber 对象

{
  type,                 // 节点类型 (元素,文本,组件)(具体的类型)
  props,               // 节点属性
  stateNode,       // 节点 DOM 对象 | 组件实例对象
  tag,                   // 节点标记(对具体类型的分类 hostRoot || hostComponent || classComponent || functionComponent)
  effects,            // 数组,存储需要更改的 fiber 对象
  effectTag,        // 当前Fiber要被执行的操作(新增,删除,修改)
  parent,             // 当前 Fiber 的父级 Fiber
  child,               // 当前 Fiber 的子级 Fiber
  sibling,            // 当前 Fiber 的下一个兄弟 Fiber
  alternate         // Fiber 备份 fiber比对时使用
}

5 Fiber树

fiber 树是 Singly Linked List Tree Structure 单链表树结构

相当于“儿子-兄弟表示法”的二叉树 ,child 指向左子节点,sibling 指向右子节点,

首先树上的每个结点是统一的,就是两个指针域,一个叫 FirstChild, 一个叫 NextSibling,第一个指针指向的是它的第一个儿子,右边的指针是指向的下一个兄弟,所有的结点都以这种方式来指向儿子和兄弟,这样就可以把整个树的结点串起来

这种结构是Fiber中使用“循环”来遍历树的关键

06. 排查一个webpack构建耗时异常的问题

最近做了一个商城项目,是在create react app创建的模版项目上跑eject命令把内部配置抛出来后魔改的,采用了Mpa+Spa的模式,拆分了多个html入口js入口

到了要上线的时候,发现在本地生产模式构建竟然需要2个小时,线上容器里跑也要三四十分钟,远远超出了正常的构建时间,影响了项目正常发布。

初步分析

综合了一下异常的现象,大概有以下几点:

  1. 构建耗时和构建页面的数量基本成正比(项目是多页的)
  2. 开发模式下构建耗时正常,生产模式下异常
  3. 只是耗时久,但是并没有报错

既然是打包的问题,那自然得从webpack身上找原因了。

结合第1个现象,同时为了方便在本地打包复现,整个过程中我只把打包其中3个页面的过程作为异常样板。

webpack打包大致有这几个阶段:依赖分析代码转换代码优化和压缩

初步分析配置文件,再结合第2点基本上可以合理推断,问题出在优化和压缩阶段(因为我们的项目中开发和生产模式构建在前两个阶段干的事情基本上差别不大)

为了佐证以上推测,先用webpack-bundle-analyzer跑了一遍,生产模式下构建产物的体积和依赖关系看起来没有很明显的问题。

定位排查

先从代码优化和压缩阶段入手,webpack4这部分基本上都走的optimization配置,这是这部分的配置代码:

optimization: {
  minimize: isEnvProduction,
  minimizer: [
    new TerserPlugin({
      terserOptions: {
        parse: {
          ecma: 8,
        },
        compress: {
          ecma: 5,
          warnings: false,
          comparisons: false,
          inline: 2,
        },
        mangle: {
          safari10: true,
        },
        keep_classnames: isEnvProductionProfile,
        keep_fnames: isEnvProductionProfile,
        output: {
          ecma: 5,
          comments: false,
          ascii_only: true,
        },
      },
      sourceMap: shouldUseSourceMap,
    }),
    new OptimizeCSSAssetsPlugin({
      cssProcessorOptions: {
        parser: safePostCssParser,
        map: shouldUseSourceMap
          ? {
              inline: false,
              annotation: true,
            }
          : false,
      },
      cssProcessorPluginOptions: {
        preset: ["default", { minifyFontValues: { removeQuotes: false } }],
      },
    }),
  ],
  splitChunks: {
    chunks: "all",
    name: false,
  },
  runtimeChunk: true,
},

重点关注optimization的几个一级配置项。

minimize是生产模式开启压缩的选项,minimizer是注册具体进行优化和压缩工作的插件及其具体配置,splitChunks是配置代码拆分规则,runtimeChunk是是否单独拆分运行时chunk的选项。

挨个来看,minimize不用管。

minimizer这里配置了TerserPluginOptimizeCSSAssetsPlugin两个插件,分别处理jscss,都有嫌疑,先待定。

splitChunks这里配置的是默认的自动拆分规则,webpack会自动把公共的依赖拆分到单独的bundle,其实从之前webpack-bundle-analyzer的分析结果比较容易看出拆分的包没有什么问题,同时也可以看到runtimeChunk也正常被拆分出来了, 基本上排除嫌疑了。

回到minimizer,想不如干,直接拿着这两个插件(只开启其中一个拆件)各跑了一遍build。在禁用OptimizeCSSAssetsPlugin的时候,构建耗时正常了!

好了,现在问题的范围缩小了,是OptimizeCSSAssetsPlugin

再来贴一遍这个插件的配置:

new OptimizeCSSAssetsPlugin({
  cssProcessorOptions: {
    parser: safePostCssParser,
    map: shouldUseSourceMap
      ? {
          inline: false,
          annotation: true,
        }
      : false,
  },
  cssProcessorPluginOptions: {
    preset: ["default", { minifyFontValues: { removeQuotes: false } }],
  },
})

先不管它是如何造成问题的,先看看这个插件做了什么事情。

也就两件事:优化css和压缩css。OptimizeCSSAssetsPlugin通过processor(默认是cssnano,基于postcss)优化css代码,再进行压缩。

到这里发挥一下“想象力”,OptimizeCSSAssetsPlugin是处理的css的,我们项目是使用scss的,也就是说构建到这一步的 时候scss到css已经转换完毕了,我们可以确定是转换后得到的css产物出了问题,这个锅OptimizeCSSAssetsPlugin不背啊,我只是进一步处理这些问题产物的!

接下来对css构建产物进行检查,对比了正常和异常的css构建产物,文件大小基本是差不多的,内容基本也是差不多的。但是为何耗时却差距几十倍上百倍?

我意识到OptimizeCSSAssetsPlugin干的一个很重要的事情被我忽略了,那就是去除重复代码!

再发挥一下“想象力”,异常的css产物中有大量重复代码,它们增加了OptimizeCSSAssetsPlugin的负担,但是被OptimizeCSSAssetsPlugin去除后看起来却很正常的一样,这样就说得通了。大量的重复代码,这是不正常的!

再次检查了css构建产物,这些代码引起了我的注意:

.m-1{margin:1px!important}
.pt-1{padding-top:1px!important}
.pl-1{padding-left:1px!important}
.pb-1{padding-bottom:1px!important}
.pr-1{padding-right:1px!important}
.p-1{padding:1px!important}
.mt-2{margin-top:2px!important}
.ml-2{margin-left:2px!important}
.mb-2{margin-bottom:2px!important}
/* 此处脑补... */

有经验的同学应该不陌生,这是一些Atom CSS,很多项目中都会使用到,一般在入口文件中统一引入作为全局样式使用,在我们的项目中是写在一个叫margin-padding.scss的文件中。

全局搜索这个文件名, 在webpack配置文件中发现了这些代码:

{
  loader: "sass-resources-loader",
  options: {
    resources: [
      path.resolve(__dirname, "../src/styles/variables.scss"),
      path.resolve(__dirname, "../src/styles/customTheme.scss"),
      path.resolve(
        __dirname,
        "../src/styles/margin-padding.scss"
      ),
    ],
  },
}

好家伙,真凶有点呼之欲出了。

记(git)忆(history)告诉我这是之前某同学为了在scss文件中自动注入变量而使用的一个loader。

variables.scss,customTheme.scss是一些项目中使用到的顶层scss变量,转换后其实不会产生任何css代码,没什么问题。

这个margin-padding.scss问题就有点大了,贴一段它的完整代码:

@for $member from 0 through 200 {
  .mt-#{$member} {
    margin-top: #{$member}px !important;
  }
  .ml-#{$member} {
    margin-left: #{$member}px !important;
  }
  .mb-#{$member} {
    margin-bottom: #{$member}px !important;
  }
  .mr-#{$member} {
    margin-right: #{$member}px !important;
  }
  .m-#{$member} {
    margin: #{$member}px !important;
  }
  .pt-#{$member} {
    padding-top: #{$member}px !important;
  }
  .pl-#{$member} {
    padding-left: #{$member}px !important;
  }
  .pb-#{$member} {
    padding-bottom: #{$member}px !important;
  }
  .pr-#{$member} {
    padding-right: #{$member}px !important;
  }
  .p-#{$member} {
    padding: #{$member}px !important;
  }
}

结合这个sass-resources-loader来看, 好家伙真的好家伙,原来我们给每个scss文件注入了2000条样式,而且它们全部被转换成了真正的css,并且经过合并,最后进入了要交到OptimizeCSSAssetsPlugin手中处理的css文件中

其实查阅sass-resources-loader的文档,文档中已经给出了警告:

Do not include anything that will be actually rendered in CSS, because it will be added to every imported Sass file.

不要包含任何实际将在CSS中呈现的内容,因为它将被添加到每个导入的Sass文件中!

到这里,基本宣告破案了。

解决

事实上,我们已经全局引入了margin-padding.scss,并且它是可以正常工作的,再通过sass-resources-loader去注入完全是多余并且错误的,所以去掉这一句就好。

去掉以后再运行全项目打包进行验证,1分钟跑完了,喜闻乐见。

后续

后来偶然间发现了speed-measure-webpack-plugin这个神器,它可以测量webpack构建期间各个阶段花费的时间,直接就可以找出瓶颈在哪,重点突破了。

我用这个工具重新跑了一遍之前的异常构建,工具很直观地显示出OptimizeCssAssetsWebpackPlugin花费了大量的时间。

回过头来看,如果一开始就使用speed-measure-webpack-plugin分析,可以省去手动定位到OptimizeCssAssetsWebpackPlugin之前所花费的时间。

在cra项目中的使用也很简单:

// /scripts/build.js
const configFactory = require("../config/webpack.config");
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

// Generate configuration
const config = configFactory("production");

// const compiler = webpack(config); 
const compiler = webpack(smp.wrap(config));

这个工具不仅可以用来排查异常,也可以对分析和优化webpack构建速度提供很好的帮助。

04. 在create-react-app(eject)模板项目基础上的多页打包配置

概述

该配置方案基于约定式,通过glob匹配文件路由,自动根据文件路径和创建web服务器(在本项目中是devServer或者是nginx)路由的映射。
加入一个新的页面时不需要额外修改配置,只需要执行gen命令:

  • yarn run gen 通过plop工具在src/pages下创建新的页面入口文件夹

然后通过start和build命令启动和构建:

  • yarn start 开发模式启动devServer,自动把入口文件路径路径映射到devServer的路由
  • yarn build 生产模式构建同时生成相应的nginx配置文件,产物可以直接接入项目的CI/CD进行升级

整体配置流程

  • 准备工作
    • 拆分入口文件目录
    • 使用glob动态获取入口文件绝对路径并且挂载在paths
    • 修改scripts下的构建脚本适配多页
    • 配置devServer的路径映射适配多页
    • 提取公共方法getMpaConfig统一处理多页相关的配置项
  • 修改webpack配置文件
    • 修改entry
    • 修改Htmlwebpackplugin
    • 修改ManifestPlugins
    • 修改output
    • 修改MiniCssExtractPlugin
  • 打包输出优化
    • 显示打包进度,优化打包体验
    • 修改output, 调整js,css以及chunk的输出目录
    • 再次修改HtmlWebpackPlugins,调整html模版输出目录

准备工作

拆分文件和动态获取路径

这里将MPA入口文件夹统一放置src/pages目录下,配合glob库动态获取每个入口的html Entryjs Entry的绝对路径, 并且挂载在config/paths.js导出的路径对象中:

// config/paths.js

// 各页面的对应的模板文件路径
const htmlEntrys = glob.sync(path.join(__dirname, "../src/pages/*/index.html"));
// 各页面的对应的模板文件路径
const jsEntrys = glob.sync(path.join(__dirname, "../src/pages/*/index.tsx"));

module.exports = {
  htmlEntrys,
  jsEntrys
  ...
}

配置devServer的路径映射

进行多页面拆分后,开发环境需要在http server中设置不同入口和路由的映射。
对于我们使用的webpack dev server来说,即配置它的historyApiFallback选项中的rewrites属性。
为了保持自动化,这里我们需要根据动态的入口路径,动态生成rewrites配置。

// config/webpackDevServer.config.js

// 动态生成rewrites配置
const mapHtmlEntry = () => {
  let rewrites = [];
  
  // 对于全局根路由,严格匹配并且指向首页入口
  // 首页入口没有前端路由分发
  rewrites.push({ from: /^\/$/, to: "/index/index.html" });
  
  // 根据入口文件夹名称设置路由映射规则
  paths.htmlEntrys.forEach((htmlEntryPath) => {
    const entryName = htmlEntryPath.match(/pages\/(.*)\/index.html/)[1];
    // 对于其他入口的根路由,注意这里只匹配开头
    // 因为其他入口可能有前端路由分发
    // 因此只要开头匹配成功,就映射到对应入口html
    let regRule = new RegExp(`^\/${entryName}`);
    const rule = { from: regRule, to: `/${entryName}/index.html` };
    
    if (entryName !== "index") rewrites.push(rule);
  });
  
  // 设置匹配失败时的fallback页面
  rewrites.push({ from: /./, to: "/error/index.html" });
  
  return rewrites;
};

module.exports = function (proxy, allowedHost) {
  return {
    historyApiFallback: {
      rewrites: mapHtmlEntry(),
    },
    ...
  }
}

修正scripts

用于构建项目的scripts中使用了checkRequiredFiles方法来检查打包入口文件是否存在,在文件缺失时会抛出错误。
这里需要将原本传入的参数由单入口修改为多入口。

// scripts/start.js && scripts/build.js

if (!checkRequiredFiles(paths.htmlEntrys.concat(paths.jsEntrys))) {
  process.exit(1);
}

提取公共方法getMpaConfig统一处理多页的配置项

接下来还需要处理webpack配置中多页相关的选项entryoutput和插件HtmlWebpackPluginsManifestPlugins来完成多页打包。
output可以通过webpack变量设置,而另外三项需要通过我们获取的动态入口路径生成,所以封装了getMpaConfig方法统一处理。
该方法接收一个webpack mode参数,读取各入口的paths,返回与多页入口相对应的entrys对象,HtmlWebpackPluginsManifestPlugins实例数组。

// config/getMpaConfig.js

const paths = require("./paths");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ManifestPlugin = require("webpack-manifest-plugin");

// Get the Mpa config
// Return Mpa entrys, htmlWebpackPlugins and manifestPlugins
function getMpaConfig(webpackEnv) {
    // some code
  return {
    entrys,
    htmlWebpackPlugins,
    manifestPlugins,
  };
}

module.exports = getMpaConfig;
entrys

多页的entrys是对象,每个key表示一个js入口的bundle名,对应的值为js Entry的绝对路径,因此在getMpaConfig中有如下代码获得entrys

  paths.jsEntrys.forEach((jsEntryPath) => {
    const entryName = jsEntryPath.match(/pages\/(.*)\/index.tsx/)[1];

    entrys[entryName] = [
      isEnvDevelopment &&
        require.resolve("react-dev-utils/webpackHotDevClient"),
      jsEntryPath,
    ].filter(Boolean);
  });
Htmlwebpackplugin

每个HtmlWebpackPlugin只会处理和生成一个html文件,因此我们需要为每一个html配置一个HtmlWebpackPlugin实例

 paths.htmlEntrys.forEach((htmlEntryPath) => {
    const entryName = htmlEntryPath.match(/pages\/(.*)\/index.html/)[1];
    const htmlWebpackPluginProdConfig = {
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true,
      },
    };


    htmlWebpackPlugins.push(
      new HtmlWebpackPlugin(
        Object.assign(
          {},
          {
            filename: `${entryName}/index.html`,
            inject: true,
            template: htmlEntryPath,
            chunks: [entryName],
          },
          isEnvProduction ? htmlWebpackPluginProdConfig : undefined
        )
      )
    );
  });
ManifestPlugins

Htmlwebpackplugin类似, 需要为每一个打包入口应用一个ManifestPlugins实例生成对应的manifest文件,在getMpaConfig中有如下代码获得ManifestPlugins

paths.htmlEntrys.forEach((htmlEntryPath) => {
    const entryName = htmlEntryPath.match(/pages\/(.*)\/index.html/)[1];
    
    manifestPlugins.push(
      new ManifestPlugin({
        fileName: `asset-${entryName}-manifest.json`,
        publicPath: paths.publicUrlOrPath,
        generate: (seed, files, entrypoints) => {
          const manifestFiles = files.reduce((manifest, file) => {
            manifest[file.name] = file.path;
            return manifest;
          }, seed);
          const entrypointFiles = entrypoints[entryName].filter(
            (fileName) => !fileName.endsWith(".map")
          );
    
          return {
            files: manifestFiles,
            entrypoints: entrypointFiles,
          };
        },
      })
    );
    
    // some code
});

修改webpack.config.js 配置文件

以上准备工作完成后,就可以修改webpack.config.js完成打包配置了。

首先在配置文件导出方法中使用getMpaConfig获得所需要的配置:

module.exports = function (webpackEnv) {
    // 获得MPA相关的配置
    const { entrys, htmlWebpackPlugins, manifestPlugins } = getMpaConfig(
        webpackEnv
    );
    
    // some code
}

直接将这几个对象应用在配置中:

module.exports = function (webpackEnv) {
    // some code
    
    return {
        ...,
        entry: entrys,
        plugins: [
            ...
        ].filter(Boolean)
         .concat(htmlWebpackPlugins)
         .concat(manifestPlugins),
    }

}

接下来需要修改output,因为单入口时,output的输出配置是死的,而现在需要根据多入口动态输出:

module.exports = function (webpackEnv) {
    // some code
    
    return {
        ...
        output: {
            ...,
            filename: isEnvProduction
                ? "[name]/[name].[contenthash:8].js"
                : isEnvDevelopment && "[name]/[name]-bundle.js",
            chunkFilename: isEnvProduction
                ? "[name]/[name].[contenthash:8].chunk.js"
                : isEnvDevelopment && "[name]/[name].chunk.js",
        }
    }

}

最后还需要修改MiniCssExtractPlugin配置,类似output,它需要根据不同html入口动态输出打包后的css文件
修改MiniCssExtractPlugin构造函数参数对象中的filenamechunkFilename[name]/[name].[contenthash:8].css[name][contenthash:8]webpack变量
[name]指当前入口文件的文件名,[contenthash:8]表示8位hash值,用于防止浏览器缓存。

打包输出优化

基于以下一些原则对打包输出的文件结构进行了约束:

  • chunksjsEntry一一绑定,MPA内部的私有资源(js、css、img等)都集中到当前文件夹下以降低服务器寻址时间
  • html入口命名都使用index.html(原因是Nginx服务器中网站入口默认都是index.html)

output

  • 最终打包出的文件结构
.
├── 0
│   ├── 0.9feccc66.chunk.js
│   ├── 0.9feccc66.chunk.js.LICENSE.txt
│   └── 0.9feccc66.chunk.js.map
├── ...
├── 30
│   ├── 9.2bc6a003.chunk.js
│   └── 9.2bc6a003.chunk.js.map
├── account
│   ├── account.18278e5b.chunk.css
│   ├── account.18278e5b.chunk.css.map
│   ├── account.29e3e08d.chunk.js
│   ├── account.29e3e08d.chunk.js.map
│   └── index.html
├── goods
│   ├── goods.18278e5b.chunk.css
│   ├── goods.18278e5b.chunk.css.map
│   ├── goods.38c6746f.chunk.js
│   ├── goods.38c6746f.chunk.js.map
│   └── index.html
├── index
│   ├── index.18278e5b.chunk.css
│   ├── index.18278e5b.chunk.css.map
│   ├── index.95abb4d4.chunk.js
│   ├── index.95abb4d4.chunk.js.map
│   └── index.html
├──usercenter
│   ├── index.html
│   ├── usercenter.0b9899c5.chunk.js
│   ├── usercenter.0b9899c5.chunk.js.map
│   ├── usercenter.18278e5b.chunk.css
│   └── usercenter.18278e5b.chunk.css.map
├── runtime~account
│   ├── runtime~account.811fc362.js
│   └── runtime~account.811fc362.js.map
├── runtime~goods
│   ├── runtime~goods.482bb187.js
│   └── runtime~goods.482bb187.js.map
├── runtime~index
│   ├── runtime~index.965f58d1.js
│   └── runtime~index.965f58d1.js.map
├── runtime~usercenter
│   ├── runtime~usercenter.a1049c67.js
│   └── runtime~usercenter.a1049c67.js.map
├── static
│   └── media
│       ├── login-icon.35d858fb.png
│       ├── logo.ea5bfd44.png
│       ├── no-enable-org.bc7034a1.png
│       └── regist-success.4001541c.png
├── service-worker.js
├── asset-account-manifest.json
├── asset-goods-manifest.json
├── asset-index-manifest.json
├── asset-usercenter-manifest.json
├── favicon.ico
├── logo192.png
├── logo512.png
├── manifest.json
├── precache-manifest.5b9d0af9d96a174ce5370a116fdc2cd3.js
└── robots.txt
  • 结构说明:account、goods、usercenter、index对应src/pages路径下的MPA入口文件夹名

07. 如何用Babel做静态分析和代码转换

如何用Babel做静态分析和代码转换

Astexplorer.net. 查看ast

Babel编译流程

  • parse。 @babel/parser 转换ast
  • transform @babel/traverse 遍历ast(前序 深度优先) @babel/types @babel/template
  • Generate @babel/generate

@babel/traverse

visit一个节点的过程: enter -> 遍历子节点(深度优先) -> exit

通过visitor函数对遍历到的ast进行处理,具体操作ast使用path的api,还可以通过state来在遍历过程中传递一些数据

path是遍历过程中的路径,会保留上下文信息,有很多属性和方法,比如:

  • path.node指向当前ast节点
  • path.parent指向父级ast节点
  • ...

这些属性和方法是获取当前节点以及它的关联节点的

  • path.scope获取当前节点的作用域信息

这个属性可以获取作用域的信息

  • path.isXxx判断当前节点是不是xx类型
  • ...
traverse(ast, {
	visitor: {
		...
	}
})

@babel/types

创建、判断ast节点,提供了xxx、isXxx、assertXxx等api

@babel/template

批量创建ast

  • Template.ast
  • ...

@babel/generator

ast转换完之后打印成目标代码字符串

@babel/code-frame

当有错误信息要打印的时候,需要打印错误位置的代码

@babel/core

集成之前的包,完成整个编译流程,从源码到目标代码,生成sourcemap

  • tansformSync
  • transformFileSync
  • ...

Babel插件

export default function(api, options,dirname) {
	return {
		inherits: parentPlugin,
		manipulateOptions(options, parserOptions) {
			options.xxx = '';
		},
		pre(file) {
			this.cache = new Map();
		},
		visitor: {
			StringLiteral(path, state) {
				this.xxxx.
			}
 		}
	}
}

插件可以做的事情

  • 自动console 文件名、行、列
  • 自动埋点
  • 自动国际化
  • 自动生成api文档
    • api文档的生成也是根据源码信息来的
  • ...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.