前端工程化项目的思考

42 阅读13分钟

这是一篇个人使用前端工程开发项目的思考,希望可以帮助到你。完全是一篇综合概念应该是很多东西,我也不清楚会有多少字,估计会对刚刚开始的人看起来比较迷,但也是没有办法的事情

  • 1.前端脚本语言开发的作者我想应该也想不到js会发展到今天这样和后端语言一较高下的时候,这一切的起源我想应该就是各大浏览器的发展,那时候刀耕火种js运行在各种浏览器上面,通过各大公司不同的js引擎,有很多不同的ast规范,各种兼容可谓是百家争鸣,现在的市场流行谷歌浏览器,然后谷歌的v8引擎,和node出现,导致js开启处理文本io流字符串各种功能,实现了与其他后端语言一样的服务。

  • 2.node的出现我们从原生的写法创建一个后端项目然后把HTML放入,比如Java的jsp PHP的视图,这些老家伙的书写方法,开始被时代所放弃,出现了基于node的工程化项目。

  1. 1.工程化的过程出现了低级打包工具 高级打包工具,所谓的低级就是只管字符的处理,不管执行,高级就是基于这些编译后的结果,融入了各种服务然后执行,编译各种写法,比如Babel的出现让我们可以使用最新的语法,只需要引入包,使用各种typescript js的变体,coffee jsx ts 这些东西的出现,把js的书写和弱项开始弥补,出现了 postcss Autoprefixer 让兼容样式写法变成了工具,使用更加方便我们可谓是站在巨人的肩膀上面。

  2. 1.通常现在的项目都是起一个所谓的cli脚手架 是一个综合性很强的集成工具包,融入了框架,样式,资源,服务,打包,编译,处理字符,使用node底层,来帮助我们开发。这一系统的融合,得益于一些流行的npm库,node是这个环境的启动者。

  3. 1.比如我们使用npm run dev所谓自定义的执行脚本命令,也是node读取当前目录或者全局指令注册下的结果,读当前的packagejson文件,然后执行script,如果里面是可执行脚本命令比如node index.js,node就会帮你,如果是自定义的比如vue-cli这样的自定义的命令,他会去依赖文件里找bin文件夹寻找可执行的命令,使用commander 插件来提高自定义cmd操作,bin文件会注册cmd命令在你安装库的使用,如果是全局安装会自动注册,如果是局部也会自动执行,在node的安装过程中,分环境比如本地和生产,很多包也是这样用做区别,安装插件也分全局或者局部,我们也可以自行注册全局的包别名。

  4. 1.使用npm run dev 会触发vue-cli serve 综上这自定义库的自定义命令并不是node自带的,但确实node提供插件可创建的东西,所谓的serve 会开启一个基于net http协议的端口 也会启动一个socket ws协议,同时在本地客户端生成通信的websocket文件脚本,通常我把cli文件下面的src叫做客户端因为这一部分不会和node有关系,内容是更新于浏览器html内容,除这之外都是服务器node提供操作的地方,但是src里的操作js 资源 图片 服务 import require async 等等所有的东西都基于高级打包工具,比如webpack。

  5. 1.webpack帮我们处理js vue css img 字体 资源 图片各种 压缩 打包 编译 框架 服务等等,把一切浏览器不认识的东西变成认识的东西,这一切的功能都来自webpack自身的强大,提供了一系统了loader plugin。入口文件 开始 就把所有资源处理调用对应的loader 在运行过程中使用plugin来拓展功能,

  6. 1.可编译js并不是他的功能,而是babel在webpack什么的拓展,比如babel-loader,这只是遇见js文件的时候就叫给他,比如vue 就给到vue-loader 然后返回js 然后又给到babel-loader ,遇到css就给到css-loader,很多loader我们不一说明,我们重点描述一下Babel。

  7. 1.交给Babel的js,我们看出一段字符串,一段在非专业人士看起来没有规律的字符串,这一段给到babel。Babel是如何处理的? 1.其实 babel 的工作流程和编译原理中的编译流程相对简单。我们可以归纳如下几个步骤: 词法分析 语法分析 代码转换 代码生成. 2.其中分为词法分析和预发分析两步可以合并成解析(parse)过程 从上图可以看到编译从开始到结束有一个最重要的东西,抽象语法树/AST 的知识,以下简称 AST,babel 编译代码的整个流程都离不开它。

抽象语法树是高级编程语言(Java、JavaScript 等)转换成机器语言的桥梁。解析器会根据 ECMAScript 标准「JavaScript 语言规范」来对代码字符串进行词法分析,拆分成一个个词法单元,再遍历各个词法单元进行语法分析构造出 AST。

词法分析 词法分析阶段是对源代码进行“分词”,它接收一段源代码,然后执行一段 tokenize 函数,把代码分割成被称为 tokens 的东西。tokens 是一个数组,由一些代码的碎片组成,比如数字、标点符号、运算符号等等等等, 词法分析处理网站 在词法分析之后,语法分析会把词法分析得到的 tokens 转化为 AST,有兴趣的可以阅读一下 babel 源码 babel 转化 AST 源码 ast处理网站 @babel/parser 包的 parse 方法传入源代码,进行词法分析合语法分析,最终生成 AST 抽象语法树 @babel/traverse 包 traverse 方法接收 AST 抽象语法树并对其进行遍历(深度遍历),在此过程中对节点进行添加、更新及移除等操作。 这是 Babel 或是其他编译器中最复杂的过程,同时也是插件将要介入工作的地方,插件部分我们后边在讲 @babel/generator 包 generator 方法接收的 AST 抽象语法树转换成字符串形式的代码,同时还会创建源码映射(sourceMap,根据传入的参数控制是否生成 sourceMap) @babel/traverse 的 traverse 转换过程是深度遍历整颗树对节点进行操作,它会访问树中的所有节点。这时候该方法第二个参数就起到作用了。这个参数是一个对象,对象每个属性是一个钩子函数。这个对象的属性值除了支持 AST 语法树节点的 type 值外,还有 enter,exit;也就是在遍历每个节点的时候会先进入 enter 钩子函数,如果存在该节点对应的钩子函数,还会执行该钩子函数,最后在访问该节点结束的时候执行 exit 钩子函数...

@babel/types,它的作用是创建、修改、删除、查找ast节点。另外从上边知道AST的节点也是分为多种类型,比如ExpressionStatement是表达式、ClassDeclaration是类声明、VariableDeclaration是变量声明等等,同样的这些类型都对应了其创建方法:t.expressionStatement、t.classDeclaration、t.variableDeclaration,也对应了判断方法:t.isExpressionStatement、t.isClassDeclaration、t.isVariableDeclaration。这个插件往往和traverse遍历插件一起使用,因为types只能对单一节点进行操作,一般是在对节点的深度遍历中使用。 babel.types 用做判断深度遍历ast时候处理对应type来提供方便

module.exports = function (babel) {
  return {
    visitor: {
      Identifier(path) {
        console.log(path.type, path.node.name);
      },
      CallExpression(path) {
        if (
          path.node.callee &&
          babel.types.isIdentifier(path.node.callee.object, { name: "console" })
        ) {
          path.remove();
        }
      },
    },
  };
};

如图的babel.type是核心插件@babel/core提供的,他会把 @babel/parser @babel/traverse @babel/generator @babel/types 集成在一起 依赖如下

"dependencies": {
    "@ampproject/remapping": "^2.1.0",
    "@babel/code-frame": "^7.18.6",
    "@babel/generator": "^7.20.5",
    "@babel/helper-compilation-targets": "^7.20.0",
    "@babel/helper-module-transforms": "^7.20.2",
    "@babel/helpers": "^7.20.5",
    "@babel/parser": "^7.20.5",
    "@babel/template": "^7.18.10",
    "@babel/traverse": "^7.20.5",
    "@babel/types": "^7.20.5",
    "convert-source-map": "^1.7.0",
    "debug": "^4.1.0",
    "gensync": "^1.0.0-beta.2",
    "json5": "^2.2.1",
    "semver": "^6.3.0"
  },
  "deprecated": false,
  "description": "Babel compiler core.",
  "devDependencies": {
    "@babel/helper-transform-fixture-test-runner": "^7.19.4",
    "@babel/plugin-syntax-flow": "^7.18.6",
    "@babel/plugin-transform-flow-strip-types": "^7.19.0",
    "@babel/plugin-transform-modules-commonjs": "^7.19.6",
    "@babel/preset-env": "^7.20.2",
    "@jridgewell/trace-mapping": "^0.3.8",
    "@types/convert-source-map": "^1.5.1",
    "@types/debug": "^4.1.0",
    "@types/gensync": "^1.0.0",
    "@types/resolve": "^1.3.2",
    "@types/semver": "^5.4.0",
    "rimraf": "^3.0.0"
  },

现在流行的vite 或者说 很多cli 都是基于Babel Babel 基于webpack 或者 rollup 提供了自己的拓展,方便他们使用,然后这个核心插件会默认开启读取.babelrc文件里的presets plugins .babelrc文件内容如下

{
  "presets": ["@babel/preset-env"],
  "plugins": ["./my-babel-plugin/index.js", "./my-babel-plugin/console.js"]
}

index.js内容如下

module.exports = function (babel) {
  return {
    visitor: {
      Identifier(path) {
        console.log(path.type, path.node.name);
      },
    },
  };
};

babel处理ast的过程中我们可以使用它规定的插件规范,提供一些自定义的功能,来使用。 babel插件的规范, 如下所示

const obj = babel.transformFileSync(file, {
  //   babelrc: true,
  plugins: [
    function MyPlugin(babel) {
      return {
        visitor: {
          Identifier(path) {
            console.log(path.type, path.node.name);
          },
        },
      };
    },
  ],
  presets: [function text() {}],
});

babel形参是babel-type提供的 path的在读取到对应ast时候触发的函数 path会获取到对应的当前代码内容对象,然后结合babel-type来验证ast类型 就可以改变当前代码的内容从而变成自己需要的返回结果。

在这里插入图片描述 在这里插入图片描述 可以创建vscode调试文件 来debug结果内容 方便我们开发独立的插件, babel还提供了插件单元测试 这个可以自行了解。

基于Babel部分的使用是完全融入了webpack你到处可以看见他的身影,就不过多讨论。

webpack 构建 了cli vite又是怎么构建的 集成了rollup rollup使用了Babel版的rollup 然后还有esbuild 字符串级别操作打包依赖工具,服务还是node。 rollup是用来发布工具库使用的,比如某一个插件js,然后打包各种兼容版本的脚本,比如umd 形式。 可以运行于import 浏览器 引入,如果注意区别了 node环境 那也可以使用 ,因为node目前支持了import。 在这里插入图片描述

webpack其实也可以完成如上打包发布更加可以使用babel来兼容es5,

在这里插入图片描述 两种操作如上图。

处理了js写法,之后启动的服务和public资源用是怎么回事了?

关于cli启动服务和资源运行

在使用的过程中,我们不知道有没有关注执行了vue-cli serve 端口就起了 然后页面也有了, 然后热更新也存在了。 让我们一步步来解释,

基于webpack的配置 首先我们肯定知道 要让命令生效 肯定是离不开node 所谓一个命令必然是执行的一个文件。

  1. vite里是起了一个net http 端口 然后结合请求与资源响应 来生成页面,比如默认很快就起来了,因为他是通过请求发送再解析文件,比如 xxx/ 根地址输入 会处理对应的路径 然后使用HTML模板 把入口文件的js执行 然后触发各种请求 然后返回js脚本 来形成单页面内容,也就是后端的node 有一个机制,对应模块变成了对应请求,各种小模块变成了整合大模块的依赖,比如lodash 依靠esbuild预编译处理打包依赖,方便后续使用直接读取,也为了保证路径的正确,重写了资源路径 设置了node module的别名。 至于他的热更新,也是自己通过实现自己的socket与页面通信脚本,每次更新就有一个新的hash产生然后响应给到浏览器页面 重新响应注入脚本;
  2. webpack是使用node服务一样的 你可以选择使用net http 也可以使用成熟的koa express 来处理资源, 因为单页面的路由处理需要变成了 history的时候就需要这些服务框架来配合, 使用不同的综合性工具开发思路与注意的地方是不同的,比如个性化都会提供一些内置的操作和指令来帮助我们使用。 比如webpack的插件和预编译是自己提供的 根据自己内置的taptable来发出钩子函数来运行。 vite是集成了rollup插件然后融入了自己的一部分,父类是继承rollup 所以学vite 必了解rollup,至于esbuild只在启动的时候发挥作用,打包部署运行编译还是rollup提高。
  3. 很多框架不同文件后缀都是得益于操作各种文件里的字符串。使用各种手段来完成对应的转化js植入。 这是工程化发展到今天我们要做的思考和准备,这一切的根据是node与ast规范 做为根本,这些东西涉及了js引擎 比如v8 执行的过程,是如何把js处理成ast树,如何通过把ast树编译成最终机器语言认识的东西,在编译的过程里,为了符合规范,提出了各种概念,比如原型,全局变量,作用域,函数,变量类型,各种操作符,这些都是v8处理后 为了我们方便书写 提出了概念,所以深入的走下去,热爱的人必然会揭开它的神秘面纱,从而使js更进一步,在各种字符串的处理中,为了方便我们需要学各种正则,如何存的更优雅,需要具备完善的数据结构知识,学习算法来帮助我们更加了解前人之伟大。 大家输写的变量如何存如何回收,如何处理,如何让函数执行提升,如何让请求发送,如何渲染页面,如何使动画更优化,如何让加载更快,如何让缓存利用起来,如何让我们的项目成为自己的艺术品,如何将js的生态和前沿的东西使用到项目里,这些思考我想回填满作为年轻人的努力青春吧, 付出多少获取多少,代码就是这样,大家分享这些。

前端未来会怎么样

大家目前涉及了pc端网页 移动端网页 客户端应用 手机app 甚至还有一些web3的技术比如区块,数字孪生,虚拟现实,3D地球,二维地图,三维地图,甚至现在的各种动画三D引擎,webgl,可以说方向众多,但我们的精力是有限的,公司是有业务范围的,所以在熟悉公司开发项目的任务,空闲时间可以学一些其他方向 给自己未来提前打好基础,比如混迹各种群来帮助你我认识流行社区的大佬。 有人说前端是不是要学后端,我只能说curd的操作你用不了一年两年就熟练,但人生有几十年,所以不能将一两年的东西使用好多年吧,在这些时间里必然要为了未知做出探索,涉及后端数据库,安全,服务器,客户端,应用,有人会问这些学的玩吗,我说肯定学不完,那为啥要卷,因为为了填满年轻时候空闲的时间,顺手学点东西,然后提高自己的见识,毕竟如此人生,学也是一辈子,不学也是一辈子,有人忙忙碌碌,就活了前二十年,有人一直坚持学习,活了后面几十年,发光发热,所以我们必然要努力的前进。